我正在编写一个程序,它根据使用反射提取的属性的数据类型动态创建Control。以下是考试科目的观点。
<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是布尔值,则动态创建的控件将是一个复选框。如果PropertyValue是一个字符串,那么动态创建的控件将是一个TextBox。如果PropertyValue是FileInfo的列表,那么将使用另一个带有OpenFileDialog的ListView和浏览按钮创建一个单独的窗口。
我能够通过创建一个实现IValueConverter的类来完成动态创建的控件,该类在XAML中指定使用。 PropertyValueConverter通过检查其数据类型将PropertyValue转换为动态创建的控件。
我的问题是当CheckBox被选中时,没有引发任何事件,并且ViewModel没有被其更改修改。我怀疑是因为XAML中的绑定是针对UserControl而不是其子控件,恰好是CheckBox。虽然可以在PropertyValueConverter中以编程方式绑定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();
}
}
--- Tab Control ---
<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>
答案 0 :(得分:3)
更简单的方法是根据您的媒体资源类型创建模板。首先,您必须添加系统命名空间才能访问所有基本类型:
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
的类型选择正确的模板。由于您要从模板中绑定父级,因此必须使用元素绑定PropertyValue
(在Access parent DataContext from DataTemplate中描述)。
答案 1 :(得分:2)
/编辑好吧,有人更快:/
这是示例(没有INotifyPropertyChanged,因为我不想写太多代码;))
public interface IViewModel
{
string PropertyName { get; set; }
}
public class StringViewModel : IViewModel
{
public string PropertyName { get; set; }
public string Content { get; set; }
}
public class BooleanViewModel : IViewModel
{
public string PropertyName { get; set; }
public bool IsChecked { get; set; }
}
public class MainViewModel
{
public ObservableCollection<IViewModel> ViewModels { get; set; }
public MainViewModel()
{
ViewModels = new ObservableCollection<IViewModel>
{
new BooleanViewModel {PropertyName = "Bool", IsChecked = true },
new StringViewModel {PropertyName = "String", Content = "My text"}
};
}
}
<Window x:Class="WpfApplication2.MainWindow"
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:WpfApplication2"
mc:Ignorable="d"
xmlns:viewModel="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<ListView ItemsSource="{Binding ViewModels}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="8">
<TextBlock Text="{Binding PropertyName}" />
<ContentControl FontSize="14" Content="{Binding .}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModel:StringViewModel}">
<TextBox Text="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:BooleanViewModel}">
<CheckBox IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>