如何创建包含其他用户控件列表的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
相关产品推荐
相关产品推荐

