简体   繁体   English

WPF自定义MultiSelector将不使用ItemTemplateSelector(但将使用ItemTemplate)

[英]WPF custom MultiSelector won't use ItemTemplateSelector (but will use ItemTemplate)

I've searched for and found many questions similar mine but none of them seem to be quite my situation. 我已经搜索并发现了许多类似我的问题,但是似乎我的情况都不是。 Admittedly, mine is something of an edge-case. 诚然,我的情况有些极端。 I'm hoping someone can spot what I'm missing here. 我希望有人能发现我在这里缺少的东西。

I've long been using a custom ItemsControl which derives from MultiSelector. 我长期以来一直在使用自MultiSelector派生的自定义ItemsControl。 I have a custom DataTemplate to draw the items in it. 我有一个自定义DataTemplate来在其中绘制项目。 And they're drawn just fine if and only if I use the ItemTemplate property on the control. 并且仅当我在控件上使用ItemTemplate属性时,它们才会绘制得很好。

But when I try instead to the ItemTemplateSelector property, my override of SelectTemplate is not being called. 但是,当我尝试使用ItemTemplateSelector属性时,没有调用我对SelectTemplate的重写。 I've verified that it's creates and then set as the control's ItemTemplateSelector. 我已验证它已创建,然后将其设置为控件的ItemTemplateSelector。 But the breakpoint for its SelectTemplate override never gets hit. 但是其SelectTemplate覆盖的断点永远不会被击中。

The net effect is that the nice shapes that were previously being drawn beautifully by my one and only DataTemplate now just show up as string names. 最终结果是,以前由我的一个唯一的DataTemplate精美绘制的漂亮形状现在显示为字符串名称。

The view I'm using is like this: 我正在使用的视图是这样的:

<UserControl x:Class="MyApp.Shapes.ShapeCanvas"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:gcs="clr-namespace:MyApp.Shapes"
             xmlns:gcp="clr-namespace:MyApp.Properties"
             xmlns:net="http://schemas.mycompany.com/mobile/net"
             >

    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/MyApp.Core;component/Resources/Styles/ShapeItemStyle.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <!-- 
            Default data template for most ShapeVm types, custom data type for PolyLineVm 
            I've removed the contents for brevity but they draw Paths objects normally
            -->

            <DataTemplate x:Key="ShapeTemplate" DataType="{x:Type gcs:ShapeVm}"/>
            <DataTemplate x:Key="PolylineTemplate" DataType="{x:Type gcs:PolyLineVm}"/>

            <!-- ShapeTemplateSelector to pick the right one -->
            <gcs:ShapeTemplateSelector x:Key="ShapeTemplateSelector"
                                       DefaultTemplate="{StaticResource ShapeTemplate}"
                                       PolyLineTemplate="{StaticResource PolylineTemplate}"/>
        </ResourceDictionary>
    </UserControl.Resources>

    <Canvas x:Name="Scene">
        <gcs:ShapesControl x:Name="ShapesControl"
                           HorizontalAlignment="Stretch"
                           VerticalAlignment="Stretch"
                           ItemContainerStyle="{StaticResource ShapeItemStyle}"
                           ItemsSource="{Binding Shapes}"
                           ItemTemplateSelector="{StaticResource ShapeTemplateSelector}"
        >
            <!-- If I use this line instead of ItemContainerStyle, It *does* pick up shape template -->
            <!-- ItemTemplate="{StaticResource ShapeTemplate}" -->

            <gcs:ShapesControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas Background="Transparent" IsItemsHost="True" />
                </ItemsPanelTemplate>
            </gcs:ShapesControl.ItemsPanel>

        </gcs:ShapesControl>
    </Canvas>
</UserControl>

Custom DataTemplateSelector 自定义DataTemplateSelector

public class ShapeTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        *** THIS NEVER EVEN GETS CALLED ***
        return item is PolyLineVm ? PolyLineTemplate : DefaultTemplate;
    }

    public DataTemplate PolyLineTemplate { get; set; }
    public DataTemplate DefaultTemplate { get; set; }
}

Custom MultiSelector ("ShapesControl") 自定义MultiSelector(“ ShapesControl”)

using System.Collections.Specialized;
using System.Windows.Controls;

namespace MyApp.Shapes
{
    // Created By: 
    // Date: 2017-08-25

    using System.Linq;
    using System.Windows;
    using System.Windows.Controls.Primitives;
    using System.Windows.Input;


    /// <summary>
    /// ShapesControl - our own version of a WPF MultiSelector.  Basically an
    /// ItemsControl that can select multiple items at once.  We need this to
    /// handle user manipulation of shapes on the ShapeCanvas and, potentially,
    /// elsewhere.
    /// </summary>
    public class ShapesControl : MultiSelector
    {
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return (item is ShapeItem);
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            // Each item we display is wrapped in our own container: ShapeItem
            // This override is how we enable that.

            return new ShapeItem();
        }

        // ...Other handlers are multi-selection overrides and removed for clarity
    }
}

Finally, the ShapeItemStyle I use to draw my custom ShapeItems 最后,我用来绘制自定义ShapeItem的ShapeItemStyle

<Style x:Key="ShapeItemStyle"
       TargetType="{x:Type gcs:ShapeItem}"
       d:DataContext="{d:DesignInstance {x:Type gcs:ShapeVm}}"
           >
  <Setter Property="SnapsToDevicePixels" Value="true" />
  <Setter Property="Background" Value="Transparent"/>
  <Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}"/>
  <Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}"/>


  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate  TargetType="{x:Type gcs:ShapeItem}">
        <Grid>
          >
          <!-- ContentPresenter for the ShapeVm that this ShapeItem contains -->

          <ContentPresenter x:Name="PART_Shape"
                            Content="{TemplateBinding ContentControl.Content}"
                            ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
                            ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
                            HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
                            VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
                            IsHitTestVisible="False"
                            SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
                            RenderTransformOrigin="{TemplateBinding ContentControl.RenderTransformOrigin}"/>
        </Grid>

      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

(Edit to add ShapeItem per request. Note that this includes selection code that interacts with the custom MultiSelector ("ShapesControl") above. I removed some of those functions from ShapesControl code for brevity as they're triggered by mouse-clicks and I couldn't see how they could possibly prevent a custom DataTemplateSelector from being invoked. But I've posted all of ShapeItem here) (编辑以根据请求添加ShapeItem。请注意,这包括与上面的自定义MultiSelector(“ ShapesControl”)交互的选择代码。为了简洁起见,我从ShapesControl代码中删除了其中一些功能,因为它们是由鼠标单击触发的,看不到它们如何可能阻止自定义DataTemplateSelector被调用。但是我在这里发布了所有ShapeItem)

namespace MyApp.Shapes
{
    [TemplatePart(Name="PART_Shape", Type=typeof(ContentPresenter))]
    public class ShapeItem : ContentControl
    {
        public ShapeVm ShapeVm => DataContext as ShapeVm;
        public ShapeType ShapeType => ShapeVm?.ShapeType ?? ShapeType.None;
        static ShapeItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata
                (typeof(ShapeItem), 
                 new FrameworkPropertyMetadata(typeof(ShapeItem)));
        }
        private bool WasSelectedWhenManipulationStarted { get; set; }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            ParentSelector?.NotifyItemClicked(this, true);
            e.Handled = true;

        }

        // The following ShapeItem manipulation handlers only handle the case of
        // moving the shape as a whole across the canvas.  These handlers do NOT
        // handle the case of resizing the shape.  Those handlers are on the
        // ShapeHandleThumb class.

        protected override void OnManipulationStarting(ManipulationStartingEventArgs e)
        {
            // The Canvas defines the coordinates for manipulation

            e.ManipulationContainer = this.FindVisualParent<Canvas>();
            base.OnManipulationStarting(e);
            e.Handled = true;
        }
        protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
        {
            Debug.Assert(e.ManipulationContainer is Canvas);
            base.OnManipulationStarted(e);

            // If this item was selected already, this manipulation might be a 
            // move.  In that case, wait until we're done with the manipulation
            // before deciding whether to notify the control.

            if (IsSelected)
                WasSelectedWhenManipulationStarted = true;
            else
                ParentSelector.NotifyItemClicked(this, false);

            e.Handled = true;
        }
        protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
        {
            Debug.Assert(e.ManipulationContainer is Canvas);
            base.OnManipulationDelta(e);
            ParentSelector.NotifyItemMoved(e.DeltaManipulation.Translation);
            e.Handled = true;
        }

        protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
        {
            Debug.Assert(e.ManipulationContainer is Canvas);
            base.OnManipulationCompleted(e);
            if (WasSelectedWhenManipulationStarted)
            {
                // If nothing moved appreciably, unselect everything.  This is how I
                // am detecting just a Tap.  I have to think there is a better way...

                var t = e.TotalManipulation.Translation;
                if (Math.Abs(t.X) < 0.0001 && Math.Abs(t.Y) < 0.0001)
                    ParentSelector.NotifyItemClicked(this, false);

            }
            e.Handled = true;
        }

        private bool IsControlKeyPressed => 
            (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;

        internal ShapesControl ParentSelector =>
            ItemsControl.ItemsControlFromItemContainer(this) as ShapesControl;

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            Debug.Assert(ReferenceEquals(
                ParentSelector.ItemContainerGenerator.ItemFromContainer(this), 
                ShapeVm));
        }

        public static readonly DependencyProperty IsSelectedProperty = 
            Selector.IsSelectedProperty.AddOwner(
                typeof(ShapeItem), 
                new FrameworkPropertyMetadata(false, OnIsSelectedChanged));

        public bool IsSelected
        {
            get
            {
                var value = GetValue(IsSelectedProperty);
                return value != null && (bool)value;
            }
            set { SetValue(IsSelectedProperty, value); }
        }
        private static void OnIsSelectedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            if (!(target is ShapeItem item))
                return;

            var evt = (bool)e.NewValue ? Selector.SelectedEvent : Selector.UnselectedEvent;
            item.RaiseEvent(new RoutedEventArgs(evt, item));
        }
    }
}

The problem is with following code: 问题在于以下代码:

protected override DependencyObject GetContainerForItemOverride()
{
    // Each item we display is wrapped in our own container: ShapeItem
    // This override is how we enable that.

    return new ShapeItem();
}

When you override GetContainerForItemOverride method, it is your code's responsibility to use ItemTemplateSelector and attach it to ItemsControl. 当重写GetContainerForItemOverride方法时,使用ItemTemplateSelector并将其附加到ItemsControl是代码的责任。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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