WPF: Hydrate Canvas with Draggable Controls at Runtime

做~自己de王妃 提交于 2021-02-08 11:02:11

问题


This is what I'm trying to accomplish:

I want to have a canvas that supports dragging and dropping arbitrary data in the style of Microsoft Visio. I've found this link that does that: http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx

However, I also want to hydrate the canvas at run-time. I found this post: https://stackoverflow.com/questions/889825/wpf-is-it-possible-to-bind-a-canvass-children-property-in-xaml

One of the solutions suggests drawing each item on it's own Canvas, which I've gotten to work. But I can't get it drag and drop to work by binding the data to an ItemControl child of the canvas. Ideally, I'd like to avoid having to use code-behind to do this. Is there a XAML solution using everything I have? My code follows:

This is the MoveThumb that I created to be the control that actually is moved.

class MoveThumb : Thumb
    {
        public MoveThumb()
        {
            DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
        }

        private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Control item = this.DataContext as Control;

            if (item != null)
            {
                double left = Canvas.GetLeft(item);
                double top = Canvas.GetTop(item);

                Canvas.SetLeft(item, left + e.HorizontalChange);
                Canvas.SetTop(item, top + e.VerticalChange);                
            }
        }
    }

This are the xaml templates I defined per the first link.

<ControlTemplate x:Key="MoveThumbTemplate" TargetType="{x:Type t:MoveThumb}">
            <Rectangle Fill="Gray"/>
        </ControlTemplate>


        <ControlTemplate x:Key="NodeItemTemplate">
            <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                <t:MoveThumb Template="{StaticResource MoveThumbTemplate}"
                Cursor="SizeAll"/>
                <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
            </Grid>
        </ControlTemplate>

        <DataTemplate x:Key="nodeTemplate" DataType="Node">            
                <ContentControl Name="NodeItem"
                                Width="50"
                                Height="50"
                                Canvas.Top="50"
                                Canvas.Left="50"
                                Template="{StaticResource NodeItemTemplate}">
                    <Ellipse Fill="Black" IsHitTestVisible="False"/>
                </ContentControl>            
        </DataTemplate>

This is the xaml to create and bind my ItemsControl in the canvas.

 <ItemsControl ItemsSource="{Binding Path=NodeCollection}" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas Background="Yellow" Width="auto" Height="auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>

                    <DataTemplate>
                        <ContentControl Name="NodeItem"
                                Width="50"
                                Height="50"
                                Canvas.Top="100"
                                Canvas.Left="100"
                                Template="{StaticResource NodeItemTemplate}">
                            <Ellipse Fill="Black" IsHitTestVisible="False"/>
                        </ContentControl>
                    </DataTemplate>

                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
                        <Setter Property="Canvas.Left" Value="{Binding Path=X}" />
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>

回答1:


The problem I found when I was doing something similar (horizontal dragable timeline) was that every item was wrapped in a ContentPresenter before it was placed on the canvas which prevented the thumb from working. Its OK if you want a readonly display, but if you want to drag it becomes a problem.

The solution was to override the GetContainerForItemOverride() method. I have included my control template, styles and the XAML for the Timeline itself.
This link really helped me. How to change the default item container for ItemsControl?

    public class Timeline : ItemsControl
        {
            protected override System.Windows.DependencyObject GetContainerForItemOverride()
            {
                return new ContentControl();
            }

            protected override void PrepareContainerForItemOverride(System.Windows.DependencyObject element, object item)
            {
                var control = element as Control;
                if (control != null)
                {
                    var myUri = new Uri("/ResourceDictionary.xaml", UriKind.Relative);
                    var dictionary = Application.LoadComponent(myUri) as ResourceDictionary;

                    if (dictionary != null)
                    {
                        control.Template = (ControlTemplate)dictionary["TimelineElementTemplate"];
                    }

                    control.DataContext = item;
                }
                base.PrepareContainerForItemOverride(element, item);
            }
        }


        <ControlTemplate x:Key="TimelineElementTemplate" TargetType="{x:Type Controls:ContentControl}">
                <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                    <TextBlock Text="{Binding Path=DataContext.DebugData}"                        
                               ToolTip="{Binding Path=DataContext.DebugData}"
                               Background="{Binding Path=DataContext.Background}"/>
                    <Control Template="{StaticResource ResizeDecoratorTemplate}" />            
                </Grid>
            </ControlTemplate>

    <Style x:Key="TimelineElementStyle" TargetType="Controls:ContentControl">
        <Setter Property="Canvas.Left">
            <Setter.Value>
                <Binding Path="LeftPos" Mode="TwoWay"/>
            </Setter.Value>
        </Setter>

        <Setter Property="Width">
            <Setter.Value>
                <Binding Path="Width" Mode="TwoWay"/>
            </Setter.Value>
        </Setter>         
    </Style>

<Style x:Key="TimelineStyle" TargetType="ItemsControl">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <Canvas Background="LightGray"></Canvas>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>


    <Controls:Timeline Style="{StaticResource TimelineStyle}" 
                               DataContext="{StaticResource timelineViewModel}"   
                               ItemContainerStyle="{StaticResource TimelineElementStyle}"
                               ItemsSource="{Binding ListOfTimelineElements}"
                               HelperClasses:DragDropHelper.IsDragSource="False" 
                               HelperClasses:DragDropHelper.IsDropTarget="True" 
                               HelperClasses:SizeObserver.Observe="True"                 
                               HelperClasses:SizeObserver.ObservedWidth="{Binding TotalWidth, Mode=OneWayToSource}" 
                               BorderBrush="Black" 
                               BorderThickness="1">

            </Controls:Timeline>


来源:https://stackoverflow.com/questions/4952836/wpf-hydrate-canvas-with-draggable-controls-at-runtime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!