在 WinUI3 项目中,我有 3 个视图
IconEditorView
、ColorEditorView
和 ImageEditorView
,每个视图都有相应的 ViewModel IconEditorViewModel
、ColorEditorViewModel
和 ImageEditorViewModel
。视图实际上是相同的,唯一的区别是无论如何都从 ViewModel 绑定的一些标签(特定的 UI 元素位于视图内的独立 UserControl 中)。
我想知道是否有任何方法能够拥有像
EditorView
这样的通用视图并附加相应的ViewModel。因为他们都实现了 IEditorViewModel
,所以他们都拥有 EditorView
工作所需的一切。目前,对于每个视图,我使用依赖属性附加相应的 ViewModel,如下所示:
public sealed partial class ImageEditorView : UserControl
{
public ImageEditorViewModel ImageEditorViewModel
{
get => (ImageEditorViewModel)GetValue(ImageEditorViewModelProperty);
set => SetValue(ImageEditorViewModelProperty, value);
}
public static readonly DependencyProperty ImageEditorViewModelProperty =
DependencyProperty.Register(
nameof(ImageEditorViewModel),
typeof(ImageEditorViewModel),
typeof(ImageEditorView),
new PropertyMetadata(default));
public ImageEditorView(ImageEditorViewModel imageEditorViewModel)
{
this.InitializeComponent();
ImageEditorViewModel = imageEditorViewModel;
}
}
愚蠢的我尝试将 ViewModel 属性类型转换为
IEditorViewModel
,但这当然不起作用,因为我需要数据绑定来在用户与视图交互时更新一些工作流指示器。
将来,我可能会添加新的编辑器,因此这种方法非常方便,只需创建实现
IEditorViewModel
的 ViewModel 并将其附加到现有通用 EditorView
的实例。
我怎样才能实现这个目标?
注意: 我正在使用 WinUI3 最新稳定版本以及 CommunityToolkit.MVVM(如果有任何相关性)。
谢谢!
您可以使用 DataTemplateSelector 来实现此目的。
假设我们有这些编辑器类:
public interface IEditor
{
string Name { get; }
}
public class IconEditor : IEditor
{
public string Name { get; } = nameof(IconEditor);
public Symbol Icon { get; set; }
}
public class ColorEditor : IEditor
{
public string Name { get; } = nameof(ColorEditor);
public Brush Color { get; set; } = new SolidColorBrush(Colors.Transparent);
}
那么自定义控件可以是:
EditorView.cs
public class EditorView : Control
{
public static readonly DependencyProperty EditorProperty =
DependencyProperty.Register(nameof(Editor),
typeof(IEditor),
typeof(EditorView),
new PropertyMetadata(default));
public static readonly DependencyProperty EditorTemplateSelectorProperty =
DependencyProperty.Register(
nameof(EditorTemplateSelector),
typeof(DataTemplateSelector),
typeof(EditorView),
new PropertyMetadata(default));
public EditorView()
{
DefaultStyleKey = typeof(EditorView);
}
public ContentPresenter? EditorPresenter { get; set; }
public IEditor Editor
{
get => (IEditor)GetValue(EditorProperty);
set => SetValue(EditorProperty, value);
}
public DataTemplateSelector EditorTemplateSelector
{
get => (DataTemplateSelector)GetValue(EditorTemplateSelectorProperty);
set => SetValue(EditorTemplateSelectorProperty, value);
}
}
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1">
<Style TargetType="local:EditorView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:EditorView">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentControl
Content="{TemplateBinding Editor}"
ContentTemplateSelector="{TemplateBinding EditorTemplateSelector}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
DataTemplateSelector
可能是这样的:
XAML 中带有字典的 DataTemplateSelector
public class StringToDataTemplateDictionary : Dictionary<string, DataTemplate>
{
}
public class EditorTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; } = new();
public StringToDataTemplateDictionary DataTemplates { get; set; } = [];
protected override DataTemplate SelectTemplateCore(object item)
{
return base.SelectTemplateCore(item);
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return item is IEditor editor
? DataTemplates.TryGetValue(editor.Name, out var template) is true
? template
: DefaultTemplate
: DefaultTemplate;
}
}
最后,我们可以使用控件了:
<Page.Resources>
<local:EditorTemplateSelector x:Key="EditorTemplateSelector">
<local:EditorTemplateSelector.DataTemplates>
<DataTemplate
x:Key="IconEditor"
x:DataType="local:IconEditor">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name}" />
<SymbolIcon Symbol="{x:Bind Icon}" />
</StackPanel>
</DataTemplate>
<DataTemplate
x:Key="ColorEditor"
x:DataType="local:ColorEditor">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name}" />
<Rectangle
Width="32"
Height="32"
Fill="{x:Bind Color}" />
</StackPanel>
</DataTemplate>
</local:EditorTemplateSelector.DataTemplates>
</local:EditorTemplateSelector>
</Page.Resources>
<StackPanel>
<Button
Click="ToggleEditorButton_Click"
Content="Toggle Editor" />
<local:EditorView
x:Name="EditorViewControl"
EditorTemplateSelector="{StaticResource EditorTemplateSelector}" />
</StackPanel>
private void ToggleEditorButton_Click(object sender, RoutedEventArgs e)
{
EditorViewControl.Editor = EditorViewControl.Editor is IconEditor
? new ColorEditor() { Color = new SolidColorBrush(Colors.SkyBlue) }
: new IconEditor() { Icon = Symbol.Home };
}