You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建包含其他用户控件列表的WPF用户控件

问题描述

已实现从ViewModel获取描述作为表单文本框标签的用户控件,现需创建类似表单,要求:

  • 每行包含Edit按钮,以及默认隐藏的Ok、Cancel按钮
  • 点击Edit按钮切换显示Ok/Cancel按钮,点击Cancel按钮恢复显示Edit按钮
  • 标签来自ViewModel属性的DescriptionAttribute,计划通过属性名控制按钮可见性
  • 需要创建三个相似表单,希望复用代码,但现有代码无法正常显示,请求完善

DescriptionInfoControl 实现

XAML 代码

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:desc="clr-namespace:StagingApp.Controls.Library.Custom"
    xmlns:converter="clr-namespace:StagingApp.Controls.Library.Converters"
    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:cal="http://www.caliburnproject.org">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.TextBlocks.xaml" />
        <ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.TextBoxes.xaml" />
        <ResourceDictionary Source="pack://application:,,,/StagingApp.Styling;component/Styles/Staging.Buttons.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <converter:BoolToVisConverter x:Key="BoolToVisConverter" />
    <Style TargetType="{x:Type desc:DescriptionInfoControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type desc:DescriptionInfoControl}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" SharedSizeGroup="DescriptionsColumn" />
                            <ColumnDefinition />
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock
                            Grid.Column="0"
                            Text="{Binding DescriptionSource.Description, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                            Style="{StaticResource DeviceInfoPropertyTextStyle}" />
                        <TextBox
                            x:Name="PART_TextBox"
                            Grid.Column="1"
                            HorizontalContentAlignment="Center"
                            Style="{StaticResource DeviceInfoTextBoxStyle}" />
                        <Grid
                            Grid.Column="2">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <StackPanel
                                Style="{StaticResource EditButtonStackPanelStyle}"
                                Grid.Column="0">
                                <Button
                                    Visibility="{Binding DescriptionSource.BindingName, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}"
                                    Style="{StaticResource DeviceInfoEditButtonStyle}">
                                    <i:Interaction.Triggers>
                                        <i:EventTrigger EventName="Click">
                                            <cal:ActionMessage MethodName="Edit">
                                                <cal:Parameter Value="{Binding DescriptionSource.Property.Name}" />
                                            </cal:ActionMessage>
                                        </i:EventTrigger>
                                    </i:Interaction.Triggers>
                                    Edit
                                </Button>
                            </StackPanel>
                            <StackPanel
                                Style="{StaticResource EditButtonStackPanelStyle}"
                                Grid.Column="0"
                                Visibility="{Binding DescriptionSource.BindingName, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}">
                                <Button
                                    Style="{StaticResource DeviceInfoEditOkButtonStyle}">
                                    <i:Interaction.Triggers>
                                        <i:EventTrigger EventName="Click">
                                            <cal:ActionMessage MethodName="OkEdit">
                                                <cal:Parameter Value="{Binding DescriptionSource.Property.Name}" />
                                            </cal:ActionMessage>
                                        </i:EventTrigger>
                                    </i:Interaction.Triggers>
                                    OK
                                </Button>
                                <Button
                                    Style="{StaticResource DeviceInfoEditCancelButtonStyle}">
                                    <i:Interaction.Triggers>
                                        <i:EventTrigger EventName="Click">
                                            <cal:ActionMessage MethodName="CancelEdit">
                                                <cal:Parameter Value="{Binding DescriptionSource.Property.Name}" />
                                            </cal:ActionMessage>
                                        </i:EventTrigger>
                                    </i:Interaction.Triggers>
                                    CANCEL
                                </Button>
                            </StackPanel>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

C# 代码

namespace StagingApp.Controls.Library.Custom;

[TemplatePart(Name = _textBoxTemplateName, Type = typeof(TextBox))]
public class DescriptionInfoControl : Control
{
    private const string _textBoxTemplateName = "PART_TextBox";
    private TextBox? _partTextBox;
    private Binding? _textBinding;

    public override void OnApplyTemplate()
    {
        _partTextBox = GetTemplateChild(_textBoxTemplateName) as TextBox;
        SetBindingPartTextbox();
    }

    private void SetBindingPartTextbox()
    {
        if (_partTextBox is TextBox tbox)
        {
            if (_textBinding is null)
            {
                BindingOperations.ClearBinding(tbox, TextBox.TextProperty);
            }
            else
            {
                tbox.SetBinding(TextBox.TextProperty, _textBinding);
            }
        }
    }

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

    public DescriptionDto DescriptionSource
    {
        get => (DescriptionDto)GetValue(DescriptionSourceProperty);
        set => SetValue(DescriptionSourceProperty, value);
    }

    // Using a DependencyProperty as the backing store for Description.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DescriptionSourceProperty =
        DependencyProperty.Register(
        nameof(DescriptionSource),
        typeof(DescriptionDto),
        typeof(DescriptionInfoControl),
        new PropertyMetadata(null, DescriptionSourceChangedCallback));

    private static readonly PropertyPath _newValuePropertyPath =
        new(typeof(DescriptionDto).GetProperty(nameof(DescriptionDto.NewValue)));

    private static void DescriptionSourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DescriptionInfoControl control = (DescriptionInfoControl)d;
        Binding? binding = null;
        if (e.NewValue is DescriptionDto description)
        {
            binding = new Binding
            {
                Path = _newValuePropertyPath,
                Source = description.Source,
                Mode = BindingMode.TwoWay
            };
        }
        control._textBinding = binding;
        control.SetBindingPartTextbox();
    }
}

DescriptionsInfoListControl 实现

XAML 代码

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:desc="clr-namespace:StagingApp.Controls.Library.Custom"
                    xmlns:models="clr-namespace:StagingApp.Controls.Library.Models">

    <Style TargetType="{x:Type desc:DescriptionsInfoListControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type desc:DescriptionsInfoListControl}">
                    <ItemsControl
                        ItemsSource="{TemplateBinding Descriptions}"
                        HorizontalContentAlignment="Stretch"
                        Grid.IsSharedSizeScope="True">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate
                                DataType="{x:Type models:DescriptionDto}">
                                <desc:DescriptionInfoControl
                                   DescriptionSource="{Binding}" />
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
</ResourceDictionary>

C# 代码

namespace StagingApp.Controls.Library.Custom;
public class DescriptionsInfoListControl : Control
{
    static DescriptionsInfoListControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(DescriptionsInfoListControl),
            new FrameworkPropertyMetadata(typeof(DescriptionsInfoListControl)));
    }

    public ReadOnlyCollection<DescriptionDto> Descriptions
    {
        get => (ReadOnlyCollection<DescriptionDto>)GetValue(DescriptionsProperty);
        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(DescriptionsInfoListControl),
            new PropertyMetadata(_descriptionsEmpty));

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DescriptionsProperty = _descriptionsPropertyKey.DependencyProperty;

    public DescriptionsInfoListControl()
    {
        DataContextChanged += OnDataContextChanged;
    }

    private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        ReadOnlyCollection<DescriptionDto> descriptions;
        if (e.NewValue is null)
        {
            descriptions = _descriptionsEmpty;
        }
        else
        {
            descriptions = DescriptionPropertyList.GetDescriptions(e.NewValue);
        }

        Descriptions = descriptions;
    }
}

内容的提问来源于stack exchange,提问作者Harlan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 00:35:19