我已经搜索并发现了许多与我类似的问题,但是似乎我的情况都不是。诚然,我的情况有些极端。我希望有人能发现我在这里缺少的东西。
我长期以来一直在使用自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));
}
}
}
答案 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是代码的责任。