简体   繁体   English

如何使用可重用的用户控件填充用户控件

[英]How to Populate a User Control with a Reusable User Control

I asked this question ( How to Toggle Visibility Between a Button and a Stack Panel Containing Two Buttons ) last week, and the answer was perfect and exactly what I was looking for.上周我问了这个问题( How to Toggle Visibility Between a Button and a Stack Panel Containing Two Buttons ),答案很完美,正是我想要的。 I realized though that I'm going to have 3 User Controls all with the very similar elements on them, so it would be better to split the row out to be re-usable.我意识到我将拥有 3 个用户控件,它们上面都有非常相似的元素,所以最好将行拆分出来以便重复使用。 But, I am having a really hard time getting the controls to display on the form.但是,我真的很难让控件显示在表单上。

Here is the end result that I am looking for:这是我正在寻找的最终结果: 在此处输入图像描述

I have created this User Control, DeviceInfoRow.xaml:我创建了这个用户控件 DeviceInfoRow.xaml: 在此处输入图像描述

And here is the XAML:这是 XAML:

<UserControl 
    x:Class="StagingApp.Main.Controls.Common.DeviceInfoRow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:common="clr-namespace:StagingApp.Presentation.ViewModels.Common;assembly=StagingApp.Presentation"
    d:DataContext="{d:DesignInstance Type=common:DeviceInfoRowViewModel}"
    mc:Ignorable="d" >

    <StackPanel
        Style="{StaticResource InfoRowStackPanelStyle}">

        <Label
            Style="{StaticResource DeviceInfoPropertyLabelStyle}"
            x:Name="InfoLabel" />
        <TextBox
            Style="{StaticResource DeviceInfoTextBoxStyle}"
            x:Name="InfoTextBox" />
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <StackPanel
                Orientation="Horizontal"
                Grid.Column="0">
                <Button
                    Command="{Binding EditCommand, Mode=OneWay}"
                    Visibility="{Binding IsEditButtonVisible, Converter={StaticResource BoolToVisConverter}}"
                    Style="{StaticResource DeviceInfoEditButtonStyle}">
                    Edit
                </Button>
            </StackPanel>
            <StackPanel
                Orientation="Horizontal"
                Grid.Column="0"
                Visibility="{Binding IsEditButtonVisible, Converter={StaticResource BoolToVisConverter}, ConverterParameter=Inverse}">
                <Button
                    Command="{Binding OkCommand, Mode=OneWay}"
                    Style="{StaticResource DeviceInfoEditOkButtonStyle}">
                    OK
                </Button>
                <Button
                    Command="{Binding CancelCommand, Mode=OneWay}"
                    Style="{StaticResource DeviceInfoEditCancelButtonStyle}">
                    CANCEL
                </Button>
            </StackPanel>
        </Grid>

    </StackPanel>

</UserControl>

Here is the ViewModel for the User Control:这是用户控件的 ViewModel:

namespace StagingApp.Presentation.ViewModels.Common;
public partial class DeviceInfoRowViewModel : BaseViewModel
{
    private string? _labelText;

    public string? LabelText
    {
        get => _labelText;
        set 
        { 
            _labelText = value;
            OnPropertyChanged(nameof(LabelText));
        }
    }

    private string? _infoTextBox;

    public string? InfoTextBox
    {
        get => _infoTextBox;
        set 
        { 
            _infoTextBox = value;
            OnPropertyChanged(nameof(InfoTextBox));
        }
    }

    private bool _isEditButtonVisible;

    public bool IsEditButtonVisible
    {
        get => _isEditButtonVisible;
        set 
        {
            _isEditButtonVisible = value;
            OnPropertyChanged(nameof(IsEditButtonVisible));
        }
    }



    [RelayCommand]
    public virtual void Ok()
    {
        IsEditButtonVisible = false;
    }

    [RelayCommand]
    public virtual void Cancel()
    {
        IsEditButtonVisible = true;
    }

    [RelayCommand]
    public virtual void Edit()
    {
        IsEditButtonVisible = true;
    }
}

The BaseViewModel just implements ObservableObject and inherits from INotifyPropertyChanged . BaseViewModel只是实现ObservableObject并继承自INotifyPropertyChanged

This is what I have so far for the KitchenInfoView, which will actually display my rows:到目前为止,这是我对 KitchenInfoView 的了解,它实际上会显示我的行:

<UserControl 
    x:Class="StagingApp.Main.Controls.InfoViews.KitchenInfoView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:viewmodels="clr-namespace:StagingApp.Presentation.ViewModels.InfoViewModels;assembly=StagingApp.Presentation"
    d:DataContext="{d:DesignInstance Type=viewmodels:KitchenInfoViewModel}"
    xmlns:local="clr-namespace:StagingApp.Main.Controls.Common"
    mc:Ignorable="d" 
    d:DesignHeight="725" 
    d:DesignWidth="780"
    Background="{StaticResource Blue}">
    <Grid Margin="20">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!-- Title -->
        <Label 
            x:Name="ValidationTitle"
            Grid.Row="0"
            Grid.Column="0"
            Grid.ColumnSpan="4"
            Style="{StaticResource DeviceInfoTitleStyle}">
            DEVICE VALIDATION
        </Label>

        <!-- Directions -->
        <TextBlock
                Grid.Row="1"
                Grid.Column="0"
                Grid.ColumnSpan="4"
                Style="{StaticResource TextDirectionStyle}">
                    Please confirm that the following information is correct. 
                    If any setting is incorrect, change the value in the text box and select "Edit". 
                    The value will then be adjusted. Once all values are correct, press 'OK'.
                    The device will then reboot.
        </TextBlock>

        <!-- Data -->
        <StackPanel>
            <ItemsControl ItemsSource="{Binding Rows}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:DeviceInfoRow />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>

        <!-- Buttons -->
        <StackPanel
            Orientation="Horizontal"
            HorizontalAlignment="Center"
            Margin="0 20 0 0"
            Grid.Row="9"
            Grid.Column="1"
            Grid.ColumnSpan="2">

            <Button
                x:Name="OK"
                IsDefault="True"
                Style="{StaticResource DeviceInfoOkButtonStyle}">
                OK
            </Button>
            <Button
                x:Name="Cancel"
                IsCancel="True"
                Style="{StaticResource DeviceInfoCancelButtonStyle}">
                CANCEL
            </Button>
        </StackPanel>

    </Grid>
</UserControl>

Finally, the KitchenInfoViewModel, looks like this at the moment:最后,KitchenInfoViewModel,目前看起来像这样:

public partial class KitchenInfoViewModel : BaseViewModel
{
    [ObservableProperty]
    [Description("Controller Name")]
    private string? _controllerName;

    [ObservableProperty]
    [Description("Controller Number")]
    private string? _controllerNumber;

    [ObservableProperty]
    [Description("BOH Server Name")]
    private string? _bohServerName;

    [ObservableProperty]
    [Description("TERMSTR")]
    private string? _termStr;

    [ObservableProperty]
    [Description("Key Number")]
    private string? _keyNumber;

    [ObservableProperty]
    [Description("IP Address")]
    private string? _ipAddress;

    [ObservableProperty]
    [Description("BOH IP Address")]
    private string? _bohIpAddress;

    private ObservableCollection<DeviceInfoRowViewModel> _rows;

    public ObservableCollection<DeviceInfoRowViewModel> Rows
    {
        get => _rows;
        set
        {
            _rows = value;
            OnPropertyChanged();
        }
    }


    public KitchenInfoViewModel()
    {
        _rows = new ObservableCollection<DeviceInfoRowViewModel>();
        var properties = typeof(KitchenInfoViewModel)
            .GetProperties();


        foreach (var property in properties)
        {
            var attribute = property.GetCustomAttribute(typeof(DescriptionAttribute));
            var description = (DescriptionAttribute)attribute;
            _rows.Add(new DeviceInfoRowViewModel()
            {
                LabelText = description?.Description.ToString(),
                InfoTextBox = ""
            });
        }
    }

}

My goal is to be able to use the DeviceInfoRow over and over again on various forms, with the content of the label coming from the description of the string properties in the VM.我的目标是能够在各种 forms 上反复使用 DeviceInfoRow,其中 label 的内容来自 VM 中字符串属性的描述。 The text box should bind to each property.文本框应绑定到每个属性。

Is this possible?这可能吗? Am I asking too much?我要求太多了吗? Am I close?我很接近吗? I've been banging my head against a wall all day on this.为此,我整天都在用头撞墙。

Thanks in advance for any help.在此先感谢您的帮助。

EDIT:编辑:

To explain what I'm trying to do a little bit better:为了解释我想做的更好一点:

This is a single window application.这是一个单独的 window 应用程序。 Depending on the type of device that this application will be deployed on will determine which initial view the ShellView will load.根据将部署此应用程序的设备类型,将确定 ShellView 将加载哪个初始视图。 There are three different configurations: Server, Kitchen, Terminal.共有三种不同的配置:服务器、厨房、终端。 But each configuration is very similar:但是每个配置都非常相似:

Kitchen Configure View厨房配置视图厨房配置视图

Server Configure View服务器配置视图服务器配置视图

Terminal Configure View终端配置视图终端配置视图

As you can see, each of the three views are very similar, even capturing the similar information.如您所见,三个视图中的每一个都非常相似,甚至捕获了相似的信息。 What is meant to happen is that after the user inputs the information into the form, they will select "Configure" and the device will automatically be configured, using the information provided.意味着发生的是,在用户将信息输入表单后,他们将 select “配置”,设备将使用提供的信息自动配置。 That information will be stored in a separate model to be used by the information.该信息将存储在单独的 model 中以供信息使用。

Once the device is finished being configured, the Device Validation form (a modal form) will pop up, alerting the user that the configuration is finished and to double check that the device was configured correctly (because these are Windows devices and sometimes things don't work the way they should).设备配置完成后,设备验证表单(模态表单)将弹出,提醒用户配置已完成并仔细检查设备配置是否正确(因为这些是 Windows 设备,有时事情不t 以他们应该的方式工作)。 Initially all of the information on the Device Validation form was static, but I had the idea to populate a text box with the device information (coming from the device).最初设备验证表单上的所有信息都是 static,但我想用设备信息(来自设备)填充一个文本框。 This way, if something is incorrect, the user can press "Edit", enter the correct information in the text box and then press "OK".这样,如果出现错误,用户可以按“编辑”,在文本框中输入正确的信息,然后按“确定”。 The application at that point will make the necessary changes to the device, and then re-display the form.此时应用程序将对设备进行必要的更改,然后重新显示表单。

Again each form has a very similar look to it, with a label (or text block), text box, and a couple of buttons for each row.同样,每个表单都具有非常相似的外观,每行都有一个 label(或文本块)、文本框和几个按钮。 The Configure forms also are very similar, with a label and text box. Configure forms 也非常相似,有一个 label 和文本框。 What I'm looking for is instead of copying and pasting each row in the XAML file, having a template row (one for Validation, one for Configure) that can then be populated with the Description attribute of the properties (for the Label portion) and bound to the individual properties (for the TextBox portion).我正在寻找的不是复制和粘贴 XAML 文件中的每一行,而是有一个模板行(一个用于验证,一个用于配置)然后可以用属性的描述属性填充(对于 Label 部分)并绑定到各个属性(对于 TextBox 部分)。

I really hope this clears things up and makes sense.我真的希望这可以解决问题并且有意义。

with the content of the label coming from the description of the string properties in the VM. label 的内容来自 VM 中字符串属性的描述。 The text box should bind to each property文本框应绑定到每个属性

You already have DeviceInfoRowViewModel encapsulating all the initial setup and user updates to the TextBox.您已经拥有将所有初始设置和用户更新封装到 TextBox 的DeviceInfoRowViewModel I think defining all of these ObservableProperties manually is going againts the automation you want to achieve by using reflection in KitchenInfoViewModel constructor!我认为手动定义所有这些 ObservableProperties 与您希望通过在 KitchenInfoViewModel 构造函数中使用反射来实现的自动化背道而驰!

Take a look at this看看这个

public partial class KitchenInfoViewModel : BaseViewModel
{
    public ObservableCollection<DeviceInfoRowViewModel> Rows { get; set; }

    public KitchenInfoViewModel()
    {
        Rows = new ObservableCollection<DeviceInfoRowViewModel>{
            new DeviceInfoRowViewModel
            {
                LabelText = "Controller Name"
            },
            new DeviceInfoRowViewModel
            {
                LabelText = "Controller Number"
            },
            new DeviceInfoRowViewModel
            {
                LabelText = "BOH Server Name"
            },
            new DeviceInfoRowViewModel
            {
                LabelText = "TERMSTR"
            },
            new DeviceInfoRowViewModel
            {
                LabelText = "Key Number"
            },
            new DeviceInfoRowViewModel
            {
                LabelText = "IP Address"
            },
            new DeviceInfoRowViewModel
            {
                LabelText = "BOH IP Address"
            }
        };
    }
}

You might say你可能会说

With properties, I can use _ipAddress directly in code, so how can I refer to it directly now?有了properties,我就可以直接在代码中使用_ipAddress了,那么现在怎么直接引用呢?

You might have these literals in.resx file您可能在 .resx 文件中有这些文字

new DeviceInfoRowViewModel
{
    LabelText = Resources._ipAddress
}

So you whenever you want ipAddress Data, you can retrieve it from Rows因此,无论何时您需要 ipAddress 数据,都可以从Rows中检索它

var ipAddressData = Rows.FirstOrDefault(item => item.LabelText == Resources._ipAddress);

You can define this as get-only property if you want to refer to it repeatedly如果你想重复引用它,你可以将它定义为 get-only 属性

private DeviceInfoRowViewModel IpAddressData => 
     Rows.FirstOrDefault(item => item.LabelText == Resources._ipAddress);

My advice is to Keep it simple , you can do the same thing in any other View/ViewModel, your UserControl is well designed and reusable, I can't think of a way simpler and requires less-code than this (compared to the current KitchenInfoViewModel you have).我的建议是保持简单,您可以在任何其他 View/ViewModel 中做同样的事情,您的 UserControl 设计精良且可重用,我想不出比这更简单且需要更少代码的方法(与当前的相比KitchenInfoViewModel 你有)。

You might say:你可能会说:

With ObservableProperties, I can just remove the property to remove the row from UI使用 ObservableProperties,我只需删除属性即可从 UI 中删除行

You can remove its definition from Rows to remove the row from UI as well.您可以从Rows中删除它的定义,从而也从 UI 中删除该行。


Back to your original question..回到你原来的问题..

Is this possible?这可能吗?

If you want to stick with your approach, you can do it like this如果你想坚持你的方法,你可以这样做

In DeviceInfoRowViewModelDeviceInfoRowViewModel

public Action<string> OnInfoChanged { set; get; } // <--------------- 1

private string? _infoTextBox;

public string? InfoTextBox
{
    get => _infoTextBox;
    set 
    { 
        _infoTextBox = value;
        OnPropertyChanged(nameof(InfoTextBox));
        OnInfoChanged?.Invoke(value); // <--------------- 2
    }
}

In KitchenInfoViewModelKitchenInfoViewModel

_rows.Add(new DeviceInfoRowViewModel()
{
    LabelText = description?.Description.ToString(),
    OnInfoChanged = newUsernput => property.SetValue(this, newUsernput, null); // <--------------- 3
});

So when user updates InfoTextBox , the action will assign the new value to the ObservableProperty.因此,当用户更新InfoTextBox时,该操作会将新值分配给 ObservableProperty。

@Harlan, I want to show you an implementation that you might be able to use for your question. @Harlan,我想向您展示一个可以用于您的问题的实现。

using System.Windows;

namespace Core2022.SO.Harlan.DescriptionShow
{
    public class DescriptionDto
    {
        public string Description { get; }

        public PropertyPath Path { get; }

        public bool IsReadOnly { get; }
        public object? Source { get; }

        public DescriptionDto(string description, PropertyPath path, bool isReadOnly, object? source)
        {
            Description = description ?? string.Empty;
            Path = path;
            IsReadOnly = isReadOnly;
            Source = source;
        }

        public DescriptionDto SetSource(object? newSource)
            => new DescriptionDto(Description, Path, IsReadOnly, newSource);

        public override string ToString() => Description;
    }
}
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;
using System.Windows;

namespace Core2022.SO.Harlan.DescriptionShow
{
    public class DescriptionPropertyList
    {
        public object Source { get; }

        public ReadOnlyCollection<DescriptionDto> Descriptions { get; }

        public DescriptionPropertyList(object source)
        {
            Source = source ?? throw new ArgumentNullException(nameof(source));

            Type sourceType = source.GetType();
            if (!typeDescriptions.TryGetValue(sourceType, out ReadOnlyCollection<DescriptionDto>? descriptions))
            {
                PropertyInfo[] properties = sourceType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
                DescriptionDto[] descrType = new DescriptionDto[properties.Length];
                for (int i = 0; i < properties.Length; i++)
                {
                    PropertyInfo property = properties[i];
                    string descr = property.GetCustomAttribute<DescriptionAttribute>()?.Description ??
                                    property.Name;
                    descrType[i] = new DescriptionDto(descr, new PropertyPath(property), !property.CanWrite, Empty);
                }
                descriptions = Array.AsReadOnly(descrType);
                typeDescriptions.Add(sourceType, descriptions);
            }

            DescriptionDto[] descrArr = new DescriptionDto[descriptions.Count];
            for (int i = 0; i < descriptions.Count; i++)
            {
                descrArr[i] = descriptions[i].SetSource(source);
            }
            Descriptions = Array.AsReadOnly(descrArr);
        }

        private static readonly object Empty = new object();
        private static readonly Dictionary<Type, ReadOnlyCollection<DescriptionDto>> typeDescriptions
            = new Dictionary<Type, ReadOnlyCollection<DescriptionDto>>();
    }
}
using System.ComponentModel;

namespace Core2022.SO.Harlan.DescriptionShow
{
    public class ExampleClass
    {
        [Description("Controller Name")]
        public string? ControllerName { get; set; }

        [Description("Controller Number")]
        public string? ControllerNumber { get; set; }

        [Description("BOH Server Name")]
        public string? BohServerName { get; set; }

        [Description("TERMSTR")]
        public string? TermStr { get; set; }

        [Description("Key Number")]
        public string? KeyNumber { get; set; }

        [Description("IP Address")]
        public string? IpAddress { get; set; }

        [Description("BOH IP Address")]
        public string? BohIpAddress { get; set; }
    }
}
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Core2022.SO.Harlan.DescriptionShow
{
    [TemplatePart(Name = TextBoxTemplateName, Type = typeof(TextBox))]
    public class DescriptionControl : Control
    {
        private const string TextBoxTemplateName = "PART_TextBox";
        private TextBox? PartTextBox;
        private Binding? TextBinding;
        public override void OnApplyTemplate()
        {
            PartTextBox = GetTemplateChild(TextBoxTemplateName) as TextBox;
            if (PartTextBox is TextBox tbox)
            {
                if (TextBinding is Binding binding)
                {
                    tbox.SetBinding(TextBox.TextProperty, binding);
                }
                else
                {
                    BindingOperations.ClearBinding(tbox, TextBox.TextProperty);
                }
            }
        }

        static DescriptionControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DescriptionControl), new FrameworkPropertyMetadata(typeof(DescriptionControl)));
        }


        /// <summary>
        /// Data source, path and description of its property.
        /// </summary>
        public DescriptionDto DescriptionSource
        {
            get => (DescriptionDto)GetValue(DescriptionSourceProperty);
            set => SetValue(DescriptionSourceProperty, value);
        }

        /// <summary><see cref="DependencyProperty"/> для свойства <see cref="DescriptionSource"/>.</summary>
        public static readonly DependencyProperty DescriptionSourceProperty =
            DependencyProperty.Register(
                nameof(DescriptionSource),
                typeof(DescriptionDto),
                typeof(DescriptionControl),
                new PropertyMetadata(null, DescriptionSourceChangedCallback));

        private static void DescriptionSourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DescriptionControl descriptionControl = (DescriptionControl)d;
            Binding? binding = null;
            if (e.NewValue is DescriptionDto description)
            {
                binding = new Binding();
                binding.Path = description.Path;
                binding.Source = description.Source;
                if (description.IsReadOnly)
                {
                    binding.Mode = BindingMode.OneWay;
                }
                else
                {
                    binding.Mode = BindingMode.TwoWay;
                }
            }
            descriptionControl.TextBinding = binding;
            if (descriptionControl.PartTextBox is TextBox tbox)
            {
                if (binding is null)
                {
                    BindingOperations.ClearBinding(tbox, TextBox.TextProperty);
                }
                else
                {
                    tbox.SetBinding(TextBox.TextProperty, binding);
                }
            }
        }
    }
}

Default Template in Themes/Generic.xaml file: Themes/Generic.xaml 文件中的默认模板:

    <Style xmlns:dsc="clr-namespace:Core2022.SO.Harlan.DescriptionShow"
           TargetType="{x:Type dsc:DescriptionControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type dsc:DescriptionControl}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <UniformGrid Rows="1">
                            <TextBlock Text="{Binding DescriptionSource.Description, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type dsc:DescriptionControl}}}"/>
                            <TextBox x:Name="PART_TextBox"/>
                        </UniformGrid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace Core2022.SO.Harlan.DescriptionShow
{
    public class DescriptionsListControl : Control
    {
        static DescriptionsListControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DescriptionsListControl), new FrameworkPropertyMetadata(typeof(DescriptionsListControl)));
        }

        /// <summary>
        /// Descriptions List
        /// </summary>
        public ReadOnlyCollection<DescriptionDto> Descriptions
        {
            get => (ReadOnlyCollection<DescriptionDto>)GetValue(DescriptionsProperty);
            private set => SetValue(DescriptionsPropertyKey, value);
        }

        private static readonly ReadOnlyCollection<DescriptionDto> descriptionsEmpty = Array.AsReadOnly(Array.Empty<DescriptionDto>());

        private static readonly DependencyPropertyKey DescriptionsPropertyKey =
            DependencyProperty.RegisterReadOnly(
                nameof(Descriptions),
                typeof(ReadOnlyCollection<DescriptionDto>),
                typeof(DescriptionsListControl),
                new PropertyMetadata(descriptionsEmpty));
        /// <summary><see cref="DependencyProperty"/> for property <see cref="Descriptions"/>.</summary>
        public static readonly DependencyProperty DescriptionsProperty = DescriptionsPropertyKey.DependencyProperty;

        public DescriptionsListControl()
        {
            DataContextChanged += OnDataContextChanged;
        }

        private DescriptionPropertyList? descriptionPropertyList;
        private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is null)
            {
                descriptionPropertyList = null;
                Descriptions = descriptionsEmpty;
            }
            else
            {
                descriptionPropertyList = new DescriptionPropertyList(e.NewValue);
            }
            Descriptions = descriptionPropertyList?.Descriptions ?? descriptionsEmpty;
        }
    }
}
<Window x:Class="Core2022.SO.Harlan.DescriptionShow.DescriptionsExampleWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Core2022.SO.Harlan.DescriptionShow"
        mc:Ignorable="d"
        Title="DescriptionsExampleWindow" Height="450" Width="800">
    <Window.Resources>
        <CompositeCollection x:Key="items">
            <local:ExampleClass ControllerName="First"/>
            <local:ExampleClass ControllerName="Second"/>
            <local:ExampleClass ControllerName="Third"/>
        </CompositeCollection>
    </Window.Resources>
    <UniformGrid Columns="2">
        <ListBox x:Name="listBox" ItemsSource="{DynamicResource items}"
                 DisplayMemberPath="ControllerName"
                 SelectedIndex="0"/>
        <ContentControl Content="{Binding SelectedItem, ElementName=listBox}">
            <ContentControl.ContentTemplate>
                <DataTemplate>
                    <local:DescriptionsListControl>
                        <Control.Template>
                            <ControlTemplate TargetType="{x:Type local:DescriptionsListControl}">
                                <ItemsControl ItemsSource="{TemplateBinding Descriptions}">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate DataType="{x:Type local:DescriptionDto}">
                                            <local:DescriptionControl DescriptionSource="{Binding}"/>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </ControlTemplate>
                        </Control.Template>
                    </local:DescriptionsListControl>
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>
    </UniformGrid>
</Window>

If you are interested in such an implementation, then ask questions - I will try to answer them.如果您对这样的实现感兴趣,请提出问题 - 我会尽力回答。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM