admin管理员组

文章数量:1026373

In a WinUI3 project, I have 3 views IconEditorView, ColorEditorView, and ImageEditorView, each with their corresponding ViewModel IconEditorViewModel, ColorEditorViewModel, and ImageEditorViewModel. The views are practically the same, the only differences are a few labels that are binded from the ViewModel anyways (the specific UI elements are in independent UserControls within the views).

I was wondering if there is any way to be able to have a generic view like EditorView and attach the corresponding ViewModel. Since they all implement IEditorViewModel they all have everything that would be necessary for EditorView to work. Currently, for each view, I attach the corresponding ViewModel by using a dependency property like so:

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

Silly me tried converting the ViewModel property type to an IEditorViewModel, but that, of course, did not work since I needed data binding for updating a few workflow indicators as the user interacts with the view.

In the future, I will likely add new editors, so this approach would be very convenient to just create the ViewModel implementing IEditorViewModel and attach it to an instance of the existing generic EditorView.

How can I achieve this?

Note: I'm using WinUI3 latest stable version along with CommunityToolkit.MVVM if that's relevant in any way.

Thanks!

In a WinUI3 project, I have 3 views IconEditorView, ColorEditorView, and ImageEditorView, each with their corresponding ViewModel IconEditorViewModel, ColorEditorViewModel, and ImageEditorViewModel. The views are practically the same, the only differences are a few labels that are binded from the ViewModel anyways (the specific UI elements are in independent UserControls within the views).

I was wondering if there is any way to be able to have a generic view like EditorView and attach the corresponding ViewModel. Since they all implement IEditorViewModel they all have everything that would be necessary for EditorView to work. Currently, for each view, I attach the corresponding ViewModel by using a dependency property like so:

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

Silly me tried converting the ViewModel property type to an IEditorViewModel, but that, of course, did not work since I needed data binding for updating a few workflow indicators as the user interacts with the view.

In the future, I will likely add new editors, so this approach would be very convenient to just create the ViewModel implementing IEditorViewModel and attach it to an instance of the existing generic EditorView.

How can I achieve this?

Note: I'm using WinUI3 latest stable version along with CommunityToolkit.MVVM if that's relevant in any way.

Thanks!

Share Improve this question edited Nov 18, 2024 at 2:58 Hector Lazarin asked Nov 16, 2024 at 20:20 Hector LazarinHector Lazarin 826 bronze badges 3
  • For a "view" that changes based on what's in the the (view) "model", I use properties such as "IsVisible" to reflect a particlular aspect of the current "context". They're simply "getters" that say a particluar element should be visible or not visible (or the FontSize is different; etc.); based on some other properties. (e.g. Infantry vs cavalry vs artillery). – Gerry Schmitz Commented Nov 17, 2024 at 19:05
  • You could use something like (model is IEditImage) to show / hide parts of the view. Or just define an abstract base type for the common bits. – Jeremy Lakeman Commented Nov 18, 2024 at 3:22
  • You could try to create a wrapper view model that contains references to the different view models you want to use. I suggest you could refer to the thread: learn.microsoft/en-us/answers/questions/1151482/… – Jeaninez - MSFT Commented Nov 18, 2024 at 6:02
Add a comment  | 

1 Answer 1

Reset to default 1

You could use DataTemplateSelector for this.

Let's say we have these editor classes:

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

then the custom control could be:

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/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft/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>

and the DataTemplateSelector could be something like this:

DataTemplateSelector with Dictionary in XAML

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

finally, we can use the control:

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

In a WinUI3 project, I have 3 views IconEditorView, ColorEditorView, and ImageEditorView, each with their corresponding ViewModel IconEditorViewModel, ColorEditorViewModel, and ImageEditorViewModel. The views are practically the same, the only differences are a few labels that are binded from the ViewModel anyways (the specific UI elements are in independent UserControls within the views).

I was wondering if there is any way to be able to have a generic view like EditorView and attach the corresponding ViewModel. Since they all implement IEditorViewModel they all have everything that would be necessary for EditorView to work. Currently, for each view, I attach the corresponding ViewModel by using a dependency property like so:

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

Silly me tried converting the ViewModel property type to an IEditorViewModel, but that, of course, did not work since I needed data binding for updating a few workflow indicators as the user interacts with the view.

In the future, I will likely add new editors, so this approach would be very convenient to just create the ViewModel implementing IEditorViewModel and attach it to an instance of the existing generic EditorView.

How can I achieve this?

Note: I'm using WinUI3 latest stable version along with CommunityToolkit.MVVM if that's relevant in any way.

Thanks!

In a WinUI3 project, I have 3 views IconEditorView, ColorEditorView, and ImageEditorView, each with their corresponding ViewModel IconEditorViewModel, ColorEditorViewModel, and ImageEditorViewModel. The views are practically the same, the only differences are a few labels that are binded from the ViewModel anyways (the specific UI elements are in independent UserControls within the views).

I was wondering if there is any way to be able to have a generic view like EditorView and attach the corresponding ViewModel. Since they all implement IEditorViewModel they all have everything that would be necessary for EditorView to work. Currently, for each view, I attach the corresponding ViewModel by using a dependency property like so:

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

Silly me tried converting the ViewModel property type to an IEditorViewModel, but that, of course, did not work since I needed data binding for updating a few workflow indicators as the user interacts with the view.

In the future, I will likely add new editors, so this approach would be very convenient to just create the ViewModel implementing IEditorViewModel and attach it to an instance of the existing generic EditorView.

How can I achieve this?

Note: I'm using WinUI3 latest stable version along with CommunityToolkit.MVVM if that's relevant in any way.

Thanks!

Share Improve this question edited Nov 18, 2024 at 2:58 Hector Lazarin asked Nov 16, 2024 at 20:20 Hector LazarinHector Lazarin 826 bronze badges 3
  • For a "view" that changes based on what's in the the (view) "model", I use properties such as "IsVisible" to reflect a particlular aspect of the current "context". They're simply "getters" that say a particluar element should be visible or not visible (or the FontSize is different; etc.); based on some other properties. (e.g. Infantry vs cavalry vs artillery). – Gerry Schmitz Commented Nov 17, 2024 at 19:05
  • You could use something like (model is IEditImage) to show / hide parts of the view. Or just define an abstract base type for the common bits. – Jeremy Lakeman Commented Nov 18, 2024 at 3:22
  • You could try to create a wrapper view model that contains references to the different view models you want to use. I suggest you could refer to the thread: learn.microsoft/en-us/answers/questions/1151482/… – Jeaninez - MSFT Commented Nov 18, 2024 at 6:02
Add a comment  | 

1 Answer 1

Reset to default 1

You could use DataTemplateSelector for this.

Let's say we have these editor classes:

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

then the custom control could be:

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/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft/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>

and the DataTemplateSelector could be something like this:

DataTemplateSelector with Dictionary in XAML

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

finally, we can use the control:

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

本文标签: cUsing the same view for different ViewModels in WinUI3Stack Overflow