gpt4 book ai didi

c# - WPF - 如何与动态创建的控件实现双向数据绑定(bind)?

转载 作者:行者123 更新时间:2023-12-02 17:40:54 24 4
gpt4 key购买 nike

我正在编写一个程序,该程序根据使用反射提取的属性的数据类型动态创建 Control。这是待检查主题的 View 。

    <ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<UserControl FontSize="14" Content="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}}"></UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

我为 ListView 中的项目创建了一个项目模板。每行由两个元素组成;标签和动态创建的控件。

例如,如果 PropertyValue 是 bool 值,则动态创建的控件将是复选框。如果 PropertyValue 是字符串,则动态创建的控件将是 TextBox。如果 PropertyValue 是 FileInfo 列表,则将使用另一个 ListView 创建一个单独的窗口,并使用 OpenFileDialog 浏览按钮。

我能够通过创建一个实现 IValueConverter 的类来完成动态创建的控件,并按照 XAML 中的指定使用该类。 PropertyValueConverter 通过检查其数据类型将 PropertyValue 转换为动态创建的控件。

我的问题是,当选中复选框时,没有引发任何事件,并且 ViewModel 不会被其更改所修改。我怀疑是因为 XAML 中的绑定(bind)是针对 UserControl 而不是针对其子控件(恰好是 CheckBox)。虽然可以在 PropertyValueConverter 中以编程方式绑定(bind) IsChecked,但是有更好的方法来解决这个问题吗?

--------修订版 1 -------

public class PropertyControl: INotifyPropertyChanged
{
public string PropertyName { get; set; }

private object propertyValue;
public object PropertyValue
{
get { return propertyValue; }
set
{
propertyValue = value;
OnPropertyChanged(nameof(PropertyValue));
}
}

#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;

[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}

/// <summary>
/// Dynamically converts between value and control given a data type - control mapping.
/// </summary>
class PropertyValueConverter: IValueConverter
{
/// <summary>
/// Converts from value to control.
/// </summary>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (int))
return new NumberTextBox {Text = value.ToString()};
if (targetType == typeof (string))
return new TextBox {Text = value.ToString()};
if (targetType == typeof (bool))
return new CheckBox {IsChecked = (bool) value};
throw new Exception("Unknown targetType: " + targetType);
}

/// <summary>
/// Converts from control to value.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (NumberTextBox))
return (value as NumberTextBox).Value;
if (targetType == typeof(TextBox))
return (value as TextBox).Text;
if (targetType == typeof(CheckBox))
return (value as CheckBox).IsChecked;
throw new Exception("Unknown targetType: " + targetType);
}
}

-------- 修订版 2 -------

public partial class SettingsWindow : Window
{
public BindingList<SettingViewModel> ViewModels { get; set; }

private SettingsManager settingsManager = new SettingsManager(new SettingsRepository());

public SettingsWindow()
{
InitializeComponent();

// Reloads the data stored in all setting instances from database if there's any.
settingsManager.Reload();
// Initialize setting view model.
ViewModels = SettingViewModel.GetAll(settingsManager);
}

private void ResetButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.Reload();
}

private void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.SaveChanges();
}
}

--- 选项卡控件 ---

<TabControl Name="ClassTabControl" TabStripPlacement="Left" ItemsSource="{Binding ViewModels}">
<TabControl.Resources>
<utilities:PropertyValueConverter x:Key="PropertyValueConverter" />
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"
Margin="8" FontSize="14"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<CheckBox FontSize="14" IsChecked="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="8" HorizontalAlignment="Center">
<Button Name="ResetButton" Padding="4" Content="Reset" FontSize="14" Margin="4"
Click="ResetButton_OnClick"></Button>
<Button Name="SaveButton" Padding="4" Content="Save" FontSize="14" Margin="4"
Click="SaveButton_OnClick"></Button>
</StackPanel>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

最佳答案

更简单的方法是根据您的属性(property)类型创建模板。首先,您必须添加系统命名空间来访问所有基本类型:

xmlns:System="clr-namespace:System;assembly=mscorlib"

现在您可以摆脱转换器并在 XAML 中完成所有操作,如下所示:

<DataTemplate>
<StackPanel x:Name="itemStackPanel" Orientation="Horizontal" Margin="8">
<!-- General part -->
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"/>
<!-- Specific (property based) part -->
<ContentPresenter Content="{Binding PropertyValue}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Boolean}">
<CheckBox IsChecked="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<!-- ... -->
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</DataTemplate>

您只需根据需要为每种可能的类型创建一个模板即可。 ContentPresenter 根据 PropertyValue 的类型选择正确的模板。由于您要从模板外部绑定(bind)到父级,因此必须使用一个元素来绑定(bind) PropertyValue (如 Access parent DataContext from DataTemplate 中所述)。

关于c# - WPF - 如何与动态创建的控件实现双向数据绑定(bind)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42666510/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com