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.
-
So ganz spontan:
Erstell einDataTemplate
für die Gedanken und einDataTemplate
für die Linien dazwischen, statt dass du einen allgemeinen Style für die Items in derListBox
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
undLineViewModel
). Da dies keine UI Elemente sind, wird WPF automatisch nach einer Möglichkeit suchen, diese darzustellen, und wird dabei auf die beidenDataTemplate
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 einerListBox
hat dies schliesslich nichts mehr zu tunGrü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.
-
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?
-
Ja, das darf man, aber man muss es richtig machen
EinItemsControl
erstellt für jedes Item einUIElement
. Um genau zu sein, wird jeweils einContentPresenter
erstellt und darin dasDataTemplate
"entpackt".Canvas.Top
undCanvas.Left
haben nur eine Wirkung direkt auf den eigenen Kindern vonCanvas
. Bei dir ist somit derContentPresenter
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 rausnehmenGrü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.
-
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.aspxGrü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.