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

时间:2018-09-05 23:17:19

标签: wpf datatemplate itemscontrol itemselector

我已经搜索并发现了许多与我类似的问题,但是似乎我的情况都不是。诚然,我的情况有些极端。我希望有人能发现我在这里缺少的东西。

我长期以来一直在使用自MultiSelector派生的自定义ItemsControl。我有一个自定义DataTemplate来在其中绘制项目。如果 并且仅当我使用控件上的ItemTemplate属性,并且它们绘制得很好。

但是当我尝试使用ItemTemplateSelector属性时,没有调用我对SelectTemplate的覆盖。我已验证它已创建,然后将其设置为控件的ItemTemplateSelector。但是其SelectTemplate覆盖的断点永远不会被击中。

最终的效果是,以前由我的一个唯一的DataTemplate精美绘制的漂亮形状现在显示为字符串名称。

我正在使用的视图是这样的:

<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>

自定义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; }
}

自定义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
    }
}

最后,我用来绘制自定义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>

(编辑以按请求添加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));
        }
    }
}

1 个答案:

答案 0 :(得分:1)

问题出在以下代码上:

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();
}

当您重写GetContainerForItemOverride方法时,使用ItemTemplateSelector并将其附加到ItemsControl是代码的责任。