.NET GUI

.NET Community rund um alle Graphical User Interface (GUI) Themen.
Willkommen bei .NET GUI. Anmeldung | Registrieren | Hilfe | Impressum | Forumsregeln
in Suchen

Custom Layouts mit WPF

Letzter Beitrag 05-12-2008 19:05 von Norbert Eder. 5 Antworten.
Seite 1 von 1 (6 Treffer)
Beiträge sortieren: Zurück Weiter
  • 05-11-2008 0:41

    • FantaMango77
    • Top 10 Mitwirkender
      Männlich
    • Registriert am 05-07-2008
    • Magdeburg, DE
    • Beiträge 55
    • Punkte 760
    • Moderator

    Custom Layouts mit WPF

    Das Layouting von Masken ist einer der wichtigsten Schritte bei der Gestaltung von WPF-Oberflächen. Dem Designer werden wird für diese Aufgabe ganze Reihe eingebauter Controls zur Verfügung gestellt. In erster Linie sind das:

    • Canvas
    • StackPanel
    • WrapPanel
    • DockPanel
    • Grid

    Jedes dieser Controls hat seine eigenen Vorzüge. Mit dem Angebot dieser Panels lässt sich bereits eine ganz beachtliche Anzahl an Layout-Szenarien abdecken. Trotzdem kann es Design-Anforderungen geben, die sich nicht so leicht mit den vorgegebenen Controls umsetzen lassen. Aber hierfür gibt es eine Lösung. Denn WPF erlaubt die Erstellung eigener Panels und damit eine Möglichkeit beliebige Layouts vorzugeben.

    Wie das geht, soll hier an einem Beispiel gezeigt werden. Die Aufgabe besteht darin, ein Layout zu entwickeln, das es erlaubt beliebige Elemente kreisförmig anzuordnen.



    Für die Erstellung eines solchen Controls bietet es sich an, eine Ableitung der Klasse Panel aus dem Namespace System.Windows.Controls abzuleiten.

    public class CirclePanel : Panel
    {
    //... }

    Die Hauptaufgabe des Entwicklers besteht darin, die Methoden MeasureOverride() und ArrangeOverride() zu überschreiben und mit der entsprechenden Layout-Logik zu versehen.

    Der Layout-Mechanismus von WPF funktioniert rekursiv über die darzustellenten Elemente in zwei Schritten. Zuerst wird die Größe der einzelnen Elemente und deren Kind-Elementen bestimmt. Danach findet die Positionierung dieser Elemente statt. Das Layout-System ruft dafür die Methoden MeasureOverride() und ArrangeOverride() auf. Aus diesem Grund müssen diese beiden Methoden überschrieben werden.

    Für das CirclePanel wird die MeasureOverride()-Methode überschrieben, um die seine Größe und die Größe seiner Unterelemente festzulegen. Es wird über diese Unterelemente iteriert und für jedes die Measure()-Methode aufgerufen. Danach wird die zur Fläche des vom jeweiligen Container-Control zur Verfügung gestellten Bereichs als verwendete Größe zurückgeliefert.

    protected override Size MeasureOverride(Size availableSize)
    {
    foreach (UIElement element in InternalChildren)
    {
    element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    }

    return base.MeasureOverride(availableSize);
    }

    In der Methode ArrangeOverride() werden nun die Kind-Elemente positioniert. Mit Hilfe eines trigonometrischen Algorithmus werden die jeweiligen Positionen bestimmt und festgelegt. Dafür werden der Radius des Kreises und die Höhe und Breite jedes Controls verwendet. Für die Positionierung der Kind-Elemente wird jeweils die Arrange()-Methode aufgerufen.

    protected override Size ArrangeOverride(Size finalSize)
    {
    if (InternalChildren.Count == 0)
    return finalSize;

    double winkel = (360.0 / InternalChildren.Count) * (Math.PI / 180);

    double aktuellerWinkel = 0;

    foreach (UIElement element in InternalChildren)
    {
    double diagonale = Math.Sqrt(Math.Pow(element.DesiredSize.Width / 2, 2) + Math.Pow(element.DesiredSize.Height/ 2, 2));
    double rx = finalSize.Width / 2 - diagonale;
    double ry = finalSize.Height / 2 - diagonale;


    Point punkt = new Point(Math.Cos(aktuellerWinkel)*rx, -Math.Sin(aktuellerWinkel)*ry);
    Point verschobenerPunkt = new Point(finalSize.Width / 2 + punkt.X - element.DesiredSize.Width / 2, finalSize.Height / 2 + punkt.Y - element.DesiredSize.Height / 2);

    element.Arrange(new Rect(verschobenerPunkt.X, verschobenerPunkt.Y, element.DesiredSize.Width, element.DesiredSize.Height));

    aktuellerWinkel += winkel;
    }

    return finalSize;
    }

    Dies ist der dazugehörende Xaml-Code:

    <local:CirclePanel>
        <Button Content="1"/>
        <Button Content="2"/>
        <Button Content="3"/>
        <Button Content="4"/>
        <Button Content="5"/>
        <Button Content="6"/>
        <Button Content="7"/>
        <Button Content="8"/>
        <Button Content="9"/>
        <Button Content="10"/>
        <Button Content="11"/>
        <Button Content="12"/>
    </local:CirclePanel>

    Hier noch weitere interessante Ressourcen zum Thema:


    Happy Layouting!

    • Beitragspunkte: 20
    • IP-Adresse ist Registriert
  • 05-11-2008 1:41 Antwort zu

    AW: Custom Layouts mit WPF

    Liest sich wie eine Übersetzung von http://jobijoy.blogspot.com/2008/04/simple-radial-panel-for-wpf-and.html ... Surprise

    Aber ganz nett, dass es auch noch mal auf deutsch zu finden ist. Wobei man dann den verwendeten Algorithmus bzw. die Mathematik dahinter auch noch (auf deutsch) erklären könnte. Zumindest ich gehöre zu denen die ungerne etwas verwenden ohne zu verstehen wie es funktioniert. Wink

    • Beitragspunkte: 20
    • IP-Adresse ist Registriert
  • 05-11-2008 10:56 Antwort zu

    • FantaMango77
    • Top 10 Mitwirkender
      Männlich
    • Registriert am 05-07-2008
    • Magdeburg, DE
    • Beiträge 55
    • Punkte 760
    • Moderator

    AW: Custom Layouts mit WPF

    Hallo Gordon,

    Danke für deinen Kommentar.

    Dass sich beide Artikel ähneln liegt wohl daran, dass dasselbe Control als Demonstrationsobjekt herhalten muss. Ich kenne den Artikel von Jobi natürlich. Sein Artikel war aber auch nur einer von mehreren, die zeigten, wie man ein RadialPanel umsetzt.
    Dein Einwand ist natürlich berechtigt. Deshalb werde ich den Artikel um Angaben weiterer Ressourcen erweitern.

    Ich habe bewusst auf eine Erklärung des Algorithmus verzichtet, da das nicht Gegenstand des Artikels sein sollte.

    Ciao,
    Jens
    • Beitragspunkte: 20
    • IP-Adresse ist Registriert
  • 05-12-2008 15:11 Antwort zu

    • Norbert Eder
    • Top 10 Mitwirkender
      Männlich
    • Registriert am 04-09-2008
    • Graz / Austria
    • Beiträge 380
    • Punkte 5.754
    • ForumsAdministrator

    AW: Custom Layouts mit WPF

    Gehören da noch zwei Grafiken dazu? Ich sehe hier aktuell nur eine. D.h. da ist die Verlinkung eventuell nicht korrekt?
    • Beitragspunkte: 20
    • IP-Adresse ist Registriert
  • 05-12-2008 17:57 Antwort zu

    • FantaMango77
    • Top 10 Mitwirkender
      Männlich
    • Registriert am 05-07-2008
    • Magdeburg, DE
    • Beiträge 55
    • Punkte 760
    • Moderator

    AW: Custom Layouts mit WPF

    Es waren zwei fehlerhafte img-Tags im Text. Danke für den Hinweis. Hab's korrigiert.
    • Beitragspunkte: 20
    • IP-Adresse ist Registriert
  • 05-12-2008 19:05 Antwort zu

    • Norbert Eder
    • Top 10 Mitwirkender
      Männlich
    • Registriert am 04-09-2008
    • Graz / Austria
    • Beiträge 380
    • Punkte 5.754
    • ForumsAdministrator

    Ich hab da noch eine Variante von mir. Dieser kann per Data Binding ein Radius, ein Winkel mitgegeben werden, als auch eine Information, ob die Elemente selbst auch im Kreis angeordnet werden sollen.

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace DotNetGui.Controls.CirclePanel
    {
        public class CirclePresentationPanel : Panel
        {
            #region Attributes
    
            private Double _right;
            private Double _left;
            private Double _top;
            private Double _bottom;
    
            #endregion Attributes
    
            #region Properties
    
            public static readonly DependencyProperty RotateProperty = DependencyProperty.Register("Rotate", typeof(bool), typeof(CirclePresentationPanel), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure));
    
            public bool Rotate
            {
                get { return (bool)GetValue(RotateProperty); }
                set { SetValue(RotateProperty, value); }
            }
    
            public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(Double), typeof(CirclePresentationPanel), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));
    
            public Double Angle
            {
                get { return (Double)GetValue(AngleProperty); }
                set { SetValue(AngleProperty, value); }
            }
    
            public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(Double), typeof(CirclePresentationPanel), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));
    
            public Double Radius
            {
                get { return (Double)GetValue(RadiusProperty); }
                set { SetValue(RadiusProperty, value); }
            }
    
            #endregion Properties
    
            #region Overrides 
    
            protected override Size MeasureOverride(Size availableSize)
            {
                Double angleChild = this.Angle / this.Children.Count;
                Double angle = this.Angle / (-2);
                Double angleRadiant;
                Point point1;
                Point point2;
                Point point3; 
                Point point4;
                
                _top = this.Radius * -1;
                _bottom = this.Radius * -1;
                _left = 0;
                _right = 0;
    
                foreach (UIElement element in this.Children)
                {
                    element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                    angleRadiant = (-1 * angle + 90) * Math.PI / 180;
    
                    point1 = new Point(Math.Cos(angleRadiant) * (this.Radius + element.DesiredSize.Height) + element.DesiredSize.Width / 2 * Math.Sin(angleRadiant), (Math.Sin(angleRadiant) * (this.Radius + element.DesiredSize.Height) - element.DesiredSize.Width / 2 * Math.Cos(angleRadiant)) * -1);
                    point2 = new Point(Math.Cos(angleRadiant) * (this.Radius + element.DesiredSize.Height) - element.DesiredSize.Width / 2 * Math.Sin(angleRadiant), (Math.Sin(angleRadiant) * (this.Radius + element.DesiredSize.Height) + element.DesiredSize.Width / 2 * Math.Cos(angleRadiant)) * -1);
                    point3 = new Point(Math.Cos(angleRadiant) * this.Radius - element.DesiredSize.Width / 2 * Math.Sin(angleRadiant), (Math.Sin(angleRadiant) * this.Radius + element.DesiredSize.Width / 2 * Math.Cos(angleRadiant)) * -1);
                    point4 = new Point(Math.Cos(angleRadiant) * this.Radius + element.DesiredSize.Width / 2 + Math.Sin(angleRadiant), (Math.Sin(angleRadiant) * this.Radius - element.DesiredSize.Width / 2 * Math.Cos(angleRadiant)) * -1);
    
                    _top = Math.Min(Math.Min(Math.Min(Math.Min(_top, point1.Y), point2.Y), point3.Y), point4.Y);
                    _bottom = Math.Max(Math.Max(Math.Max(Math.Max(_bottom, point1.Y), point2.Y), point3.Y), point4.Y);
                    _left = Math.Min(Math.Min(Math.Min(Math.Min(_left, point1.X), point2.X), point3.X), point4.X);
                    _right = Math.Max(Math.Max(Math.Max(Math.Max(_right, point1.X), point2.X), point3.X), point4.X);
    
                    angle += angleChild;
                }
                return new Size(Math.Min(_right - _left, availableSize.Width), Math.Min(_bottom - _top, availableSize.Height));
            }
    
            protected override Size ArrangeOverride(Size finalSize)
            {
                Double angleRadiant;
                Double angleChild = this.Angle / (this.Children.Count);
                Double angle = this.Angle / -2;
                
                foreach (UIElement element in this.Children)
                {
                    angleRadiant = (angle - 90) * Math.PI / 180;
    
                    Rect rect = new Rect(new Point(0, 0), new Size(element.DesiredSize.Width, element.DesiredSize.Height));
                    element.Arrange(rect);
    
                    TransformGroup transformGroup = new TransformGroup();
    
                    if (this.Rotate)
                        transformGroup.Children.Add(new RotateTransform(angle, element.DesiredSize.Width / 2, element.DesiredSize.Height));
    
                    transformGroup.Children.Add(new TranslateTransform(Math.Cos(angleRadiant) * this.Radius - element.DesiredSize.Width / 2 - _left, Math.Sin(angleRadiant) * this.Radius - element.DesiredSize.Height - _top));
                    element.RenderTransform = transformGroup;
    
                    angle += angleChild;
                }
                return finalSize;
            }
    
            #endregion Overrides
        }
    }

    Ein Demo-Fenster könnte so aussehen:

    <Window x:Class="DotNetGui.Controls.CircelPanel.Demo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DotNetGui.Controls.CirclePanel;assembly=DotNetGui.Controls.CirclePanel"
        Title="Circle Presentation Panel Demo" Height="544" Width="352">
        <Window.Resources>
            <Style TargetType="{x:Type Image}">
                <Setter Property="Width" Value="75"/>
            </Style>
            <Style x:Key="TitleStyle" TargetType="{x:Type TextBlock}">
                <Setter Property="Margin" Value="25"/>
                <Setter Property="FontSize" Value="14"/>
                <Setter Property="FontWeight" Value="Bold"/>
            </Style>
        </Window.Resources>
        <StackPanel Orientation="Vertical">
            <TextBlock Text="Circle Presentation Panel Demo" HorizontalAlignment="Center" Style="{StaticResource TitleStyle}"/>
            <TextBlock Text="Radius (valid Range: 10 - 500)"/>
            <Slider x:Name="RadiusSlider" Maximum="500" Minimum="10"/>
            <TextBlock Text="Angle (valid Range: 0 - 360)"/>
            <Slider x:Name="AngleSlider" Maximum="360" Minimum="0"/>
            <TextBlock Text="Rotation"/>
            <ComboBox x:Name="RotateBox">
                <ComboBoxItem Content="True"/>
                <ComboBoxItem Content="False" IsSelected="True"/>
            </ComboBox>
            <local:CirclePresentationPanel Radius="{Binding ElementName=RadiusSlider, Path=Value}" Angle="{Binding ElementName=AngleSlider,Path=Value}" Margin="20" Rotate="{Binding ElementName=RotateBox,Path=Text}">
                <Image Source="Images/1.jpg"/>
                <Image Source="Images/2.jpg"/>
                <Image Source="Images/3.jpg"/>
                <Image Source="Images/4.jpg"/>
                <Image Source="Images/5.jpg"/>
                <Image Source="Images/6.jpg"/>
            </local:CirclePresentationPanel>
        </StackPanel>
    </Window>

    Wie dies dann aussieht, ist im Anhang ersichtlich. Verbesserungsmöglichkeiten sind natürlich gegeben.


    Abgelegt unter: ,
    • Beitragspunkte: 5
    • IP-Adresse ist Registriert
Seite 1 von 1 (6 Treffer)
Powered by Community Server (Commercial Edition)    Hosting powered by 69° media solutions