我正在研究WPF程序,用于编辑专有脚本类。该模型由以下类组成:
public MethodCall
{
public Dictionary<string, object> Parameters { get; }
public MethodCall()
{
Parameters = new Dictionary<string, object>();
}
public MethodCall(Dictionary<string, object> parameters)
{
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters);
}
public void Execute(Client client)
{
// Does stuff
}
}
public class Script
{
public List<Request> Requests { get; }
public void Execute(Client client)
{
foreach (var request in Requests)
{
request.Execute(client);
}
}
}
要显示包含Script
请求的CallMethod
,我具有以下View Model类。
public class ScriptViewModel : INotifyPropertyChanged
{
public Script Script { get; set; } // Setter raises PropertyChanged
public ObservableCollection<RequestViewModel> { get; }
}
public class RequestViewModel : INotifyPropertyChanged
{
public Request Request { get; set; } // Setter raises PropertyChanged
public ObservableCollection<ParameterViewModel> Parameters { get; }
}
public class ParameterViewModel : INotifyPropertyChanged
{
public bool IsDirty { get; set; } // Raises PropertyChanged
public string Name { get; set; } // Raises PropertyChanged
public object Value { get; set; } // Raises PropertyChanged
}
当前,我正在使用DataGrid
控件来显示和编辑Parameters
集合。但是,由于Value
属性是一个对象,因此它可以是任何对象,包括对象数组。我的问题是如何在WPF应用程序中编辑此数据。我正在使用DataGridTextColumn
来显示Name属性,并且工作正常。当前,我还使用DataGridTextColumn
来显示Value
属性,但是效果不是很好。
我想我想使用DataGridTemplateColumn' for the
值property, but I'd need to use different templates depending upon what the particular type is. If the type isn't an array, I'd use a TextBox, but if it is an Array, I'd probably use another
DataGrid`。
如何编写XAML?
答案 0 :(得分:0)
考虑到这个问题有多小的吸引力,我最终提出了自己的解决方案。我在这里分享它,以防其他人在类似情况下可以使用它。
我首先要做的是在App.xaml中定义许多不同的DataTemplates
:
<DataTemplate x:Key="ObjectArrayTemplate"
DataType="system:Array">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<DataGrid x:Name="ParameterItemsGrid"
Grid.Column="0"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeRows="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
Initialized="OnArrayDataGridInitialized">
<DataGrid.Columns>
<DataGridTemplateColumn CellTemplateSelector="{StaticResource ValueDataSelector}"
Width="*" />
<DataGridTemplateColumn Width="52">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Button Command="local:EditorCommands.DeleteItem"
CommandParameter="{Binding Path=Item, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}}"
Content="Delete"
Height="25"
Margin="5"
VerticalAlignment="Top" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1">
<Button Content="Add Item"
Command="local:EditorCommands.AddItem"
Margin="5" />
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ValueArrayTemplate"
DataType="{x:Type system:Array}">
<DataGrid Initialized="OnArrayDataGridInitialized">
<DataGrid.Columns>
<DataGridTextColumn Header="Item" />
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
<DataTemplate x:Key="ValueTemplate"
DataType="system:Object">
<TextBox Initialized="OnValueTextBoxInitialized" />
</DataTemplate>
第一个DataTemplate
用于显示类型为程序中定义的对象数组的参数。第二个用于显示简单类型的对象数组(int,字符串等)。第三个模板用于显示类型为简单类型的参数或值为简单类型的项目。
我不会显示针对不同程序特定类的其他模板。关于这些的重要一点是,我在所有这些类上都实现了INotifyPropertyChanged
,并且在绑定到UpdateSourceTrigger
时必须设置PropertyChanged
。我做了所有这些工作,因此绑定将是2种方式并起作用。
在2 Initialized
和单个简单类型DataGrids
中定义的TextBox
事件处理程序中可以找到实现此目的的技巧:
private void OnArrayDataGridInitialized(object sender, EventArgs e)
{
var valueGrid = (DataGrid)sender;
var parameterVm = (ParameterViewModel)valueGrid.DataContext;
// Bind the value grid's ItemsSource property to the ParameterViewModel's Items property.
var itemsBinding = new Binding("Items")
{
Source = parameterVm,
ValidatesOnDataErrors = true
};
valueGrid.SetBinding(DataGrid.ItemsSourceProperty, itemsBinding);
// Bind the value grid's SelectedItem property to the ParameterViewModel's CurrentItem property.
var currentItemBinding = new Binding("CurrentItem")
{
Mode = BindingMode.TwoWay,
Source = parameterVm,
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus
};
valueGrid.SetBinding(DataGrid.SelectedItemProperty, currentItemBinding);
// Bind the value grid's SelectedIndex property to the ParameterViewModel's CurrentItemIndex property.
var currentItemIndexBinding = new Binding("CurrentItemIndex")
{
Mode = BindingMode.TwoWay,
Source = parameterVm,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
valueGrid.SetBinding(DataGrid.SelectedIndexProperty, currentItemIndexBinding);
}
private void OnValueTextBoxInitialized(object sender, EventArgs e)
{
TextBox textBox = (TextBox)sender;
DataGridRow parentRow = VisualTreeHelpers.FindAncestor<DataGridRow>(textBox);
if (!(parentRow.Item is ParameterViewModel parameterVm))
return;
var valueBinding = new Binding("Value")
{
Mode = BindingMode.TwoWay,
Source = parameterVm,
ValidatesOnDataErrors = true
};
textBox.SetBinding(TextBox.TextProperty, valueBinding);
}
}
此代码位于App.xaml.cs文件中,并使用VisualTreeHelpers
类,您可以在this post on Rachel Lim's blog中找到该类。
如您所见,OnArrayDataGridInitialized
事件处理程序找到父DataGrid
控件,然后检索其DataContext
并将其转换为ParameterViewModel
。由此,它创建所需的绑定以将视图连接到对象。
在PrameterViewModel
类中,我必须添加一个名为ObservableCollection<object>
的{{1}}属性,如果该属性是一个数组,它将保存各个数组元素。我还添加了一个名为Items
的{{1}}属性和一个名为object
的{{1}}属性。这些在上面的XAML和CurrentItem
事件处理程序中被引用。
int
属性的设置器现在看起来像这样:
CurrentItemIndex
在构造函数中,初始化Initialized
集合后,我订阅了它的Value
事件;这是我处理这些事件的方式:
public object Value
{
get => _value;
set
{
if (_value == value)
return;
if (_value is INotifyPropertyChanged npc)
npc.PropertyChanged -= OnItemPropertyChanged;
_value = value;
RaisePropertyChanged();
npc = _value as INotifyPropertyChanged;
if (npc != null)
npc.PropertyChanged += OnItemPropertyChanged;
IsDirty = true;
RaisePropertyChanged(nameof(IsValid));
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(nameof(Value));
}
最后,我必须对Items
类进行一些更改才能使所有内容正确显示和更新。这是CollectionChanged
构造函数:
private void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (object item in e.OldItems)
{
if (item is INotifyPropertyChanged npc)
npc.PropertyChanged -= OnItemPropertyChanged;
}
}
if (e.NewItems != null)
{
foreach (object item in e.NewItems)
{
if (item is INotifyPropertyChanged npc)
npc.PropertyChanged += OnItemPropertyChanged;
}
}
这是构造函数调用的ParameterViewModel
方法:
ParameterViewModel
就是这样。在XAML中无法完成全部操作,因为 public ParameterViewModel(
string objectName,
string method,
string name,
Type type,
object value,
bool isNameReadonly = false) : this(findContext)
{
if (string.IsNullOrEmpty(objectName))
throw new ArgumentNullException(nameof(objectName));
if (string.IsNullOrEmpty(method))
throw new ArgumentNullException(nameof(method));
_name = name ?? throw new ArgumentException(nameof(name));
_type = type ?? throw new ArgumentNullException(nameof(type));
_elementType = _type.GetElementType() ?? _type;
_value = value;
if (_value != null && _type.IsArray && ((Array)_value).Length > 0)
LoadItems(_value, _elementType);
RaisePropertyChanged(nameof(IsValid));
}
控件不像普通控件那样参与可视树结构。程序运行时,您可以使用Snoop正常导航到所有内容,但是绑定不能正常继承。这可以正常工作,并且使用用于自定义类型的自定义模板,用户可以轻松阅读和浏览所有内容。