Я искал и нашел много вопросов, похожих на мои, но ни один из них, похоже, не совсем подходит для моей ситуации. По общему признанию, мое является чем-то вроде крайнего случая. Я надеюсь, что кто-то может определить, что мне здесь не хватает.
Я давно использую пользовательский ItemsControl, производный от MultiSelector. У меня есть собственный 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; }
}
Custom 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
}
}
Наконец, ShapeItemStyle, который я использую для рисования своих пользовательских ShapeItems
<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));
}
}
}