简体   繁体   中英

How to draw a list of paths on a canvas?

In my WPF MVVM application I have a ListBox with Canvas as its ItemsPanel . The ListBox's items are dynamically created by the user when he clicks a button - the ListBox.ItemsSource is a list of Elements (custom type) stored in my MainViewModel , which is the DataContext of my MainWindow .

Currently my Element class describes simple objects with X and Y coordinates (so that it could be drawn on the Canvas of my ListBox) and some data called ShapeGeometry to be drawn as a path. This is its DataTemplate :

<DataTemplate DataType="{x:Type localvm:Element}">
    <Control IsEnabled="{Binding IsSelected,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}">
        <Control.Template>
            <ControlTemplate TargetType="Control">
                <Viewbox Stretch="Uniform"> <!-- Paths need to be wrapped in a Canvas + Viewbox to be movable, stretchable and rotatable -->
                    <Canvas>
                        <Path x:Name="Path" Fill="#FFAA0000" Data="{Binding ShapeGeometry}" Stretch="Uniform"/>
                    </Canvas>
                </Viewbox>
                <ControlTemplate.Triggers>
                    <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
                        <Setter TargetName="Path" Property="IsHitTestVisible" Value="False"/>
                    </DataTrigger>                                    
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Control.Template>
    </Control>
</DataTemplate>

As you can see, this allows me to draw only one specific Path per ListBoxItem. Instead, I'd like my ListBoxItem to be a ComplexElement containing a list of Element items, which means the drawn item would now consist of a various number of Paths. Now, my idea on how to achieve this would be to define ListBoxItem as ListBox with ViewBox + Canvas as its ItemsPanel , just as it is on the upper level, but it seems overly complicated and could perhaps prove to be a little inefficient (I intend to have tens if not a few hundreds of items). Is there a simpler way around it? Can I somehow avoid having ListBoxes in every ListBoxItem ?

If you want to reduce overhead and have one list, you should be able to swap out the ListBox with a DataGrid control.

Then add the Canvas to your RowDetailsTemplate . Or you can put a second ListBox in the RowDetailsTemplate.

I advise you to use a customized control (which has a ListBox or anything you want) as the DataTemplate for ListBox.ItemTemplate.In that way,you can reduce the complexity of the ListBox and the cutomized control may meet your needs by implementing your own logic.

1.Define the control class

public class MyListItem : Control
{
   //Define the property/event/control logic you want

   static MyListItem ()
   {
     //Remeber this to override the default style
     DefaultStyleKeyProperty.OverrideMetadata(typeof(MyListItem ), new FrameworkPropertyMetadata(typeof(MyListItem )));
   }
}

2.Define the Template

<Style TargetType="{x:Type my:MyListItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type my:MyListItem}">
                <Border>
                   <ListBox ItemsSource={Binding YourDetailList}>
                      <ListBox.ItemTemplate>
                       //Anything you want here
                      </ListBox.ItemTemplate>
                   </ListBox>
                </Boder>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

3.Use it

<ListBox>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <my:MyListItem/>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

BTW

1.I do recommend you NOT use canvas as the ItemsPanel for the ListBox ,it may disable the the virtualizing which may reduce the performance when the ItemsSource is huge.

2.It seems that you want a nested ListBox,so if the ItemTemplate varies,you can use the 'ItemTemplateSelector',check this MSDN .But remember the selector may be 'static' which means the datatemplate may be chosen only once in initialization.

3.Besides,if you want the Item changes its own behavior in some cases,try to use the Triggers in your Template or do it in the definition class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM