WPF Listbox und ItemStyle



  • Hallo Leute

    Ich arbeite gerade an einem Mindmapprogramm. Die Darstellung meiner Minds funktioniert. Diese werden über eine Listbox dargestellt die ich angepasst habe.

    <DataTemplate DataType="{x:Type vm:MindViewModel}">
                <Control:MindControl
                    Cursor="Hand"
                    MouseDown="MindControl_MouseDown"
                    MouseMove="MindControl_MouseMove"
                    MouseUp="MindControl_MouseUp"/>
            </DataTemplate>
    
             <Style x:Key="noScrollViewerListBoxStyle" TargetType="ListBox">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListBox">
                            <Canvas 
                                Background="{TemplateBinding Background}"
                                IsItemsHost="True"/>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    
            <Style x:Key="listBoxItemStyle" TargetType="ListBoxItem">
                <Setter Property="Canvas.Left" 
                        Value="{Binding X}"/>
                <Setter 
                    Property="Canvas.Top" 
                    Value="{Binding Y}"/>
                <Setter 
                    Property="IsSelected" 
                    Value="{Binding IsSelected}"/>
    
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListBoxItem">
    
                            <Border 
                                Name="Border"
                                BorderThickness="1"
                                Padding="5"
                                CornerRadius="5">
                                <ContentPresenter />
                            </Border>
                            <ControlTemplate.Triggers>
    
                                <Trigger Property="IsSelected" Value="true">
                                    <Setter 
                                        TargetName="Border" 
                                        Property="BorderBrush"
                                        Value="Blue"
                                        />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
    
    <ListBox  Name="mapField"
              Background="White"
              ItemsSource="{Binding Path=Minds}"
              Style="{StaticResource noScrollViewerListBoxStyle}"
              ItemContainerStyle="{StaticResource listBoxItemStyle}"/>
    

    Jetzt brauche ich noch Verbindungslinien zwischen den Minds. Leider habe ich keine Ahnung wie ich das machen soll.

    Eine zweite Listbox mit dem ItemStyle für die Linien würde die andere überdecken oder?
    Und zwei ItemStyles werden wohl auch nicht möglich sein.

    Vl. hat ja jemand eine Lösung oder eine andere Variante.

    Danke im Voraus.


  • Administrator

    So ganz spontan:
    Erstell ein DataTemplate für die Gedanken und ein DataTemplate für die Linien dazwischen, statt dass du einen allgemeinen Style für die Items in der ListBox erstellst.

    <Window.Resources>
      <DataTemplate DataType="{x:Type my:MindViewModel}">
        <!-- ... -->
      </DataTemplate>
      <DataTemplate DataType="{x:Type my:LineViewModel}">
        <!-- ... -->
      </DataTemplate>
    </window.Resources>
    

    In die ListBox steckst du nun beide Objekttypen ( MindViewModel und LineViewModel ). Da dies keine UI Elemente sind, wird WPF automatisch nach einer Möglichkeit suchen, diese darzustellen, und wird dabei auf die beiden DataTemplate bei den Ressourcen treffen.

    Vielleicht gibt es da auch noch schönere Möglichkeiten. Es ist zumindest ganz sicher mal fragwürdig, ob du weiterhin bei der ListBox bleiben möchtest. Erscheint mir irgendwie der falsche Weg zu sein. Mit einer ListBox hat dies schliesslich nichts mehr zu tun 🙂

    Grüssli



  • Danke für die schnelle Antwort.
    So wollte ich es ja auch machen. Bin jedoch schon daran gescheitert die Bindungen für die Position herzustellen.
    Mein jetziger/voriger Ansatz war

    <UserControl.Resources>
            <Style x:Key="mindControlStyle" TargetType="{x:Type Control:MindControl}">
                <Setter Property="Canvas.Left"
                        Value="{Binding X}"/>
                <Setter
                    Property="Canvas.Top"
                    Value="{Binding Y}"/>
            </Style>
    
            <DataTemplate DataType="{x:Type vm:MindViewModel}">
                <Control:MindControl
                    Cursor="Hand"
                    MouseDown="MindControl_MouseDown"
                    MouseMove="MindControl_MouseMove"
                    MouseUp="MindControl_MouseUp"
                    Style="{Binding mindControlStyle}">
                </Control:MindControl>
            </DataTemplate>
    </UserControl.Resources>
    
    ...
                     <Canvas Name="mapField" 
                                Background="White"
                                Height="2000"
                                Width="3000">
                        <ItemsControl ItemsSource="{Binding Minds}">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <Canvas IsItemsHost="True" />
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </Canvas>
    

    Doch immer landet mein Control bei X/Y = 0.


  • Administrator

    Dein Style-Binding scheint mir irgendwie verkehrt zu sein.
    1. Ist es gar nicht nötig, da du mit TargetType im Style bereits gesagt hast, auf welche Elemente der Style angwendet werden soll.
    2. Wäre es wenn schon eher "{StaticResource mindControlStyle}" und nicht ein Binding.

    Wieso überhaupt den Umweg über den Style? Könntest die Bindings ja auch gleich in das DataTemplate reinnehmen.

    Grüssli



  • Weil ich es so auch nicht hinbekomme.

    <DataTemplate DataType="{x:Type vm:MindViewModel}">
                <Control:MindControl
                    Cursor="Hand"
                    MouseDown="MindControl_MouseDown"
                    MouseMove="MindControl_MouseMove"
                    MouseUp="MindControl_MouseUp"
                    Canvas.Left="{Binding Path=X}"
                    Canvas.Top="{Binding Path=Y}">
                </Control:MindControl>
            </DataTemplate>
    

    Darf ich überhaupt Canvas nehmen?


  • Administrator

    Ja, das darf man, aber man muss es richtig machen 🙂
    Ein ItemsControl erstellt für jedes Item ein UIElement . Um genau zu sein, wird jeweils ein ContentPresenter erstellt und darin das DataTemplate "entpackt". Canvas.Top und Canvas.Left haben nur eine Wirkung direkt auf den eigenen Kindern von Canvas . Bei dir ist somit der ContentPresenter im Weg.

    Dies kann man allerdings umgehen:

    <ItemsControl ItemsSource="{Binding Minds}">
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <Canvas IsItemsHost="True" />
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <ItemsControl.ItemsContainerStyle>
        <Style>
          <Setter Property="Canvas.Left" Value="{Binding X}" />
          <Setter Property="Canvas.Top" Value="{Binding Y}" />
        </Style>
      </ItemsControl.ItemsContainerStyle>
    </ItemsControl>
    

    Im DataTemplate natürlich noch die X und Y Bindings rausnehmen 😉

    Grüssli



  • Danke. Habe es jetzt hinbekommen. Dafür habe ich aber die IsSelected-Property nicht mehr. Bei meinen Nachforschungen stieß ich dann doch wieder auf eine Listbox.
    Da aber das Problem. Ich brauche zwei ItemsControls oder Listboxen. Eine für die Minds die andere für die Lines. Die Listboxen würden sich jedoch überdecken? Müsste ja wieder den Style verändern.
    Mit den ItemsControls würde das nicht passieren.


  • Administrator

    Meine ursprünglich Idee war nicht, dass du mehrere Controls nimmst und diese übereinander tust. Packe einfach die beiden Objekte in die gleiche Liste. Hier mal ein einfaches Beispiel:

    ////////////////////////////////////////////////////////////////////////////////
    // TestDataViewModel.cs : C# file
    
    using System.Windows.Media;
    
    namespace WpfTest
    {
      class TestDataViewModel
      {
        //----------------------------------------------------------------------------//
        // Properties                                                                 //
        //----------------------------------------------------------------------------//
    
        #region Properties
    
        public int X { get; set; }
        public int Y { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
    
        public Brush Color { get; set; }
        public string Text { get; set; }
    
        #endregion
      }
    
    } // WpfTest
    
    ////////////////////////////////////////////////////////////////////////////////
    // OtherDataViewModel.cs : C# file
    
    using System.Windows.Media;
    
    namespace WpfTest
    {
      class OtherDataViewModel
      {
        //----------------------------------------------------------------------------//
        // Properties                                                                 //
        //----------------------------------------------------------------------------//
    
        #region Properties
    
        public int X { get; set; }
        public int Y { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
    
        public Brush Color { get; set; }
        public string Text { get; set; }
    
        #endregion
      }
    
    } // WpfTest
    
    <Window x:Class="WpfTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:my="clr-namespace:WpfTest"
            Title="MainWindow" Height="400" Width="800">
        <Window.Resources>
            <DataTemplate DataType="{x:Type my:TestDataViewModel}">
                <TextBlock Background="{Binding Color}" Text="{Binding Text}"
                           Width="{Binding Width}" Height="{Binding Height}" />
            </DataTemplate>
            <DataTemplate DataType="{x:Type my:OtherDataViewModel}">
                <Grid Width="{Binding Width}" Height="{Binding Height}">
                    <Ellipse Fill="{Binding Color}" />
                    <ContentPresenter Content="{Binding Text}" VerticalAlignment="Center" HorizontalAlignment="Center" />
                </Grid>
            </DataTemplate>
        </Window.Resources>
        <DockPanel>
            <ItemsControl ItemsSource="{Binding}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas IsItemsHost="True" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="Canvas.Left" Value="{Binding X}" />
                        <Setter Property="Canvas.Top" Value="{Binding Y}" />
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>
        </DockPanel>
    </Window>
    
    ////////////////////////////////////////////////////////////////////////////////
    // MainWindow.xaml.cs : C# file
    
    using System.Windows;
    using System.Collections.ObjectModel;
    using System.Windows.Media;
    
    namespace WpfTest
    {
      public partial class MainWindow
        : Window
      {
        private readonly ObservableCollection<object> m_viewModels;
    
        public MainWindow()
        {
          InitializeComponent();
    
          m_viewModels = new ObservableCollection<object>
          {
            new TestDataViewModel()
            {
              Color = Brushes.Red,
              Height = 20,
              Width = 100,
              X = 10,
              Y = 10,
              Text = "Hello"
            },
            new TestDataViewModel()
            {
              Color = Brushes.Green,
              Height = 30,
              Width = 80,
              X = 30,
              Y = 200,
              Text = "Green!"
            },
            new OtherDataViewModel()
            {
              Color = Brushes.Blue,
              Height = 25,
              Width = 50,
              X = 200,
              Y = 100,
              Text = "Blue!"
            }
          };
    
          this.DataContext = m_viewModels;
        }
      }
    }
    

    Wie ich schon sagte. Empfinde ich diese Lösung als etwas unschön. Aktuell fällt mir aber gerade nichts besseres ein, ausser mit viel zusätzlichem Code. Z.B. denke ich gerade an einen Artikel, aber weiss nicht mehr, ob der was taugt:
    http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
    http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
    http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx
    http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part4.aspx

    Grüssli



  • Danke für deine Geduld. Der Link scheint auf den ersten Blick genau das richtige zu sein.
    Mittlerweile bin ich auch davon überzeugt eine andere Lösung zu suchen. Denn mit der jetzigen komme ich nicht weiter.



  • Der DiagramDesginer hat mehr sehr geholfen. Danke nochmal.
    Dennoch habe ich jetzt ein anderes Problem.
    Meine Mindmap ist ein UserControl. Dieses wird in einem TabControl angezeigt. Es soll möglich sein mehrere Mindmaps gleichzeit zu bearbeiten. Das funktioniert nur nicht ganz so wie ich es mir vorgestellt habe.
    Denn ich setzte meine Minds immer auf die selbe Instanz des UserControls?
    Die Mindmap vom ersten Tab wird mir auch im zweiten (usw.) angezeigt.

    Der Cast im XAML.
    Das MindmapTemplate ist vereinfacht für Testzwecke.

    <DataTemplate  DataType="{x:Type vm:MindmapVM}">
            <vw:MindmapView/>
        </DataTemplate>
        <DataTemplate x:Key="MindmapTemplate">
            <DockPanel Width="120">
                <Button 
            Command="{Binding Path=CloseCommand}"
            Content="X"
            Cursor="Hand"
            DockPanel.Dock="Right"
            Focusable="False"
            FontFamily="Courier" 
            FontSize="9"
            FontWeight="Bold"  
            Margin="0,1,0,0"
            Padding="0"
            VerticalContentAlignment="Bottom"
            Width="16" Height="16" 
            />
                <ContentPresenter 
            Content="{Binding Path=DisplayName}" 
            VerticalAlignment="Center" 
            />
            </DockPanel>
        </DataTemplate>
    

    Die Bindung an das TabControl

    <TabControl Grid.Column="1" Grid.Row="1" 
                        DataContext="{Binding Source={StaticResource ' mainWindowVM'}}" 
                        ItemsSource="{Binding  Path=Mindmaps}" 
                        ItemTemplate="{Binding Source={StaticResource MindmapTemplate}}"
                        IsSynchronizedWithCurrentItem="True"/>
    

    Meine Idee wäre jetzt die Elemente am Canvas zu Löschen und die Mindmap neu zu zeichnen. Gibt es aber vl. einen schöneren Weg?

    PS:Behoben. Hab mir nochmal den Snippet von dir angesehen.


Anmelden zum Antworten