我创建了一个包含工具栏和Datagrid的复合用户控件,并将它们公开为公共属性。有没有办法在工具栏中添加新按钮并在XAML中为Datagrid设置DataTemplate,而不是在代码隐藏文件中实现它们,如果我在另一个窗口或用户控件中使用此用户控件?
我找到了类似的链接here,但不知道该怎么做。请帮忙。
这是Xaml:
<UserControl x:Class="CRUDDataGrid1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ToolBarTray Grid.Row="0" >
<ToolBar x:Name="tb">
<Button x:Name="Add" Content="Add">
</Button>
</ToolBar>
</ToolBarTray>
<DataGrid Grid.Row="1" x:Name="dg">
</DataGrid>
</Grid>
</UserControl>
以下是代码隐藏:
public partial class CRUDDataGrid1 : UserControl
{
public ToolBar ToolBar { get; set; }
public DataGrid DataGrid { get; set; }
public ObservableCollection<DataGridColumn> Columns { get; private set; } //edited
public CRUDDataGrid1()
{
InitializeComponent();
ToolBar = tb;
DataGrid = dg;
Columns = dg.Columns; //edited
}
}
我想在另一个用户控件中使用此用户控件,如下所示:
<UserControl x:Class="UserControl1" ...>
<Grid>
<local:CRUDDataGrid1>
<local:CRUDDataGrid1.ToolBar>
<Button x:Name="Delete" Content="Delete">
</Button>
</local:CRUDDataGrid1.ToolBar>
<local:CRUDDataGrid1.DataGrid ItemsSource="{Binding Customers}">
<local:CRUDDataGrid1.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding XPath=@FirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding XPath=@LastName}" />
<local:CRUDDataGrid1.Columns>
</local:CRUDDataGrid1.DataGrid>
</local:CRUDDataGrid1>
</Grid>
</UserControl>
答案 0 :(得分:2)
1前言
拥有一个拥有 ToolBar 的子控件,并希望该子控件的父控件将工具栏项添加到子项拥有的ToolBar中,这是一个很糟糕的迹象 设计。对您而言,主要和最重要的建议是重新考虑您的软件设计,以避免这种共享/拆分初始化。
在几乎任何情况下,您都希望最顶层控件拥有的工具栏(如主窗口)或文档窗口(如果您的应用程序具有MDI或浮动窗口)。 工具栏项目将从该窗口内的相应控件中收集;例如,复制/粘贴/等。来自文档编辑器控件的操作,用于从其他位置创建或加载新文档的操作等。
旁注:通常,这样的设计是因为新手WPF程序员想要以老式的方式使用Click事件处理程序来实现按钮操作。这样的点击事件 处理程序创建代码依赖项,只要它们只包含在一个(自定义)控件中,一切都很好。但是,只要这不再可行 (例如,当一个动作应该显示为工具栏按钮或者应该通过菜单触发相同的动作时),尝试坚持使用Click事件处理程序将导致复杂的代码,即使对于简单的UI也会导致严重的头痛...
WPF中避免那些讨厌的Click事件处理程序的机制是命令,或者更具体地说是RoutedCommands。公平地说,必须注意 RoutedCommands 有自己的 share of challenges。然而,许多优秀的人写了很多有趣和重要的事情,关于使用WPF的 RoutedCommands 以及如何扩展其功能,所以 如果您想要/需要了解更多信息,我在这里只能提供合理的建议是使用谷歌的权力。
2回答问题,但没有解决潜在的设计问题
要创建一个 ToolBar ,其中包含在不同位置定义的工具栏项集合,而在同一 ToolBarTray 中使用多个工具栏带是不可取的,工具栏项集合需要在某些时候合并为一个列表。这可以在代码隐藏中以某种方式完成,也可以在自定义IMultiValueConverter的帮助下在XAML中完成。
自定义 IMultiValueConverter - 让我们称之为 MergeCollectionsConverter - 将是 与任何数据类型无关。它只需要一些 IEnumerables 并将所有元素添加到结果列表中。它甚至接受不是 IEnumerable 的对象,这些对象本身将被添加到结果列表中。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Documents;
namespace MyStuff
{
public class MergeCollectionsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null) return null;
List<object> combinedList = new List<object>();
foreach (object o in values)
{
if (o is IEnumerable)
combinedList.AddRange( ((IEnumerable) o).Cast<object>() );
else
combinedList.Add(o);
}
return combinedList;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
我还假设 CRUDDataGrid1 中的 ToolBar 应该来自两个工具栏项集合。具有默认工具栏项的第一个集合在 CRUDDataGrid1 中定义。第二个集合应允许其他控件在默认项后添加其他工具栏项;因此,该集合必须可公开访问。
根据问题中的示例代码,您的CRUDDataGrid1类可能如下所示(仅考虑工具栏,它不代表完整的类):
CRUDDataGrid1.cs:
public partial class CRUDDataGrid1 : UserControl, INotifyPropertyChanged
{
public ObservableCollection<object> AdditionalToolbarItems { get { return _additionalToolbarItems; } }
private readonly ObservableCollection<object> _additionalToolbarItems = new ObservableCollection<object>();
public event PropertyChangedEventHandler PropertyChanged;
public UserControl1()
{
InitializeComponent();
_additionalToolbarItems.CollectionChanged +=
(sender, eventArgs) =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("AdditionalToolbarItems"));
};
...other constructor code...
}
}
CRUDDataGrid1.xaml:
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<ToolBar.Resources>
<DataTemplate DataType="{x:Type My:UseCommand}">
<Button
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding Command}"
CommandTarget="{Binding Target}"
CommandParameter="{Binding Parameter}"
Content="{Binding Command.Text}"
/>
</DataTemplate>
<My:MergeCollectionsConverter x:Key="convToolbarItems" />
<x:Array x:Key="defaultToolbarItems" Type="{x:Type sys:Object}">
<My:UseCommand Command="ApplicationCommands.New" />
<My:UseCommand Command="ApplicationCommands.Cut" />
<My:UseCommand Command="ApplicationCommands.Paste" />
</x:Array>
</ToolBar.Resources>
<ToolBar.ItemsSource>
<MultiBinding Converter="{StaticResource convToolbarItems}">
<Binding Source="{StaticResource defaultToolbarItems}" />
<Binding Path="AdditionalToolbarItems" ElementName="crudDataGrid1" />
</MultiBinding>
</ToolBar.ItemsSource>
</ToolBar>
</ToolBarTray>
<DataGrid x:Name="dg" />
</DockPanel>
第一个集合是一个静态资源&#39;在 ToolBar 的资源目录中, 由资源键&#34; defaultToolbarItems &#34;标识。第二个是由 CRUDDataGrid1 的属性 AdditionalToolbarItems 提供的集合。使用&lt; MultiBinding&gt; 使用上述转换器,合并列表绑定到 ToolBar 的 ItemsSource 。
查看 AdditionalToolbarItems 属性的C#源代码,您会注意到 INotifyPropertyChanged 实现以及 CollectionChanged 的处理程序 事件。这是为什么?请记住, AdditionalToolbarItems 是一个只读属性。在完全构造CRUDDataGrid1控件时,数据绑定已经完成 set和 AdditionalToolbarItems 已由多重绑定处理。它永远不会 再次处理,因为财产本身永远不会改变它的价值(它会 总是引用相同的 ObservableCollection )。每当 AdditionalToolbarItems 集合的内容具有&lt; MultiBinding&gt; 重新评估绑定属性时 更改后,代码需要侦听 CollectionChanged 事件并触发 每当 AdditionalToolbarItems 的内容发生变化时, PropertyChanged 事件,这反过来将导致&lt; MultiBinding&gt;重新评估绑定属性。
您还会注意&lt; My:UseCommand&gt; 元素的使用,而不是使用&lt; Button&gt; 。好吧,你可以使用&lt; Button&gt; ,它也可以使用。直到您的应用程序想要一次使用多个工具栏共享相同的默认按钮 - 在这种情况下您遇到问题:按钮是一个控件,因此具有一个父UI元素。你无法分享 几个工具栏中的按钮控件,因为控件只能由一个父UI元素作为子元素拥有。因此,使用 RoutedCommands 而不是按钮控件 (另一个同样重要的原因,如果您阅读下面第3部分中的“真实”解决方案,将会变得明显)。不过,从技术上讲,没有什么可以阻止你宣布&lt; Button&gt; 元素 - 您甚至可以将&lt; My:UseCommand&gt; 与&lt; Button&gt; (和其他元素混合使用,只要这些元素可以在工具栏中呈现)。< / p>
UseCommand 是一个非常小而简单的类,它允许您告诉使用哪个命令(加上可选的 CommandTarget 和 CommandParameter ,如果需要的话):
namespace MyStuff
{
public class UseCommand
{
public System.Windows.Input.ICommand Command { get; set; }
public System.Windows.IInputElement Target { get; set; }
public object Parameter { get; set; }
}
}
ToolBar需要 DataTemplate 来正确显示存储在 UseCommand 中的命令及其参数。您可以将此 DataTemplate 视为 ToolBar 的一部分 上面的XAML代码中的资源字典。
有了这些功能,在 UserControl1 中使用 CRUDDataGrid1 并添加其他工具栏项可能如下所示:
<UserControl x:Class="UserControl1" ...>
<Grid>
<local:CRUDDataGrid1>
<local:CRUDDataGrid1.AdditionalToolbarItems x:Name="cdg">
<My:UseCommand Command="ApplicationCommands.Close" CommandTarget="{Binding ElementName=cdg}" />
<My:UseCommand Command="ApplicationCommands.New" CommandTarget="{Binding ElementName=cdg}" />
</local:CRUDDataGrid1.AdditionalToolbarItems>
...
</local:CRUDDataGrid1.DataGrid>
</Grid>
</UserControl>
对于我的示例代码,我使用了System.Windows.Input.ApplicationCommands提供的命令。你可以自己滚动你自己的命令(我们将在下面看到)。另请注意 CommandTarget 属性的演示用法。是否需要使用此属性需要了解RoutedCommands的工作方式,并且主要取决于UI的可视/逻辑树中哪个元素为该特定命令建立了处理程序。
3使用RoutedCommands解决设计问题和问题
阅读第2部分后,您应该已经认识到 RoutedCommands 将帮助您将用户可调用操作的提供与实际UI表示中的任何组件分开,这可以帮助您避免恶作剧来自不同来源的 ToolBar 的有些复杂的构成。因为, CRUDDataGrid1 基本上需要为您的GUI提供的是工具栏(或菜单或任何其他命令调用者)的命令。
从我的源代码中可以看出, CRUDDataGrid1 负责执行&#34; 添加&#34;行动,而 UserControl1 负责&#34; 删除&#34;行动。 这两个操作都应出现在同一个工具栏中。
让我们看看&#34; 添加&#34; CRUDDataGrid1 的操作。首先,要通过 RoutedCommand 调用此操作,显然需要提供适当的 RoutedCommand 对象。您可以选择.NET提供的 RoutedCommands 之一(如ApplicationCommands,ComponentCommand和NavigationCommand中所声明的那样)。 但是,这并不总是一个好主意。诸如 ApplicationCommands.Copy 之类的常用命令几乎可以由支持剪贴板操作的任何控件执行,并且知道哪个实际控件将处理这样的命令的调用需要知道 RoutedCommands 通过可视树路由,以及logcial focus如何影响此路由。因此,有时它是 更容易将自己的 RoutedCommand 定义为公共静态属性 - 我们将在此处为&#34; 添加&#34;动作:
public partial class CRUDDataGrid1 : UserControl
{
public static readonly RoutedCommand AddCommand = new RoutedCommand("CRUDDataGridCommand.Add", typeof(CRUDDataGrid1));
public UserControl1()
{
InitializeComponent();
CommandBindings.Add(
new CommandBinding(
AddCommand,
OnExecutedAddCommand,
CanExecuteAddCommand
)
);
...other constructor code...
}
private void CanExecuteAddCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ...here your code that decides whether the "Add" command can execute
(and thus whether any button which uses this command will be enabled/disabled)
}
private void CanExecuteAddCommand(object sender, ExecutedRoutedEventArgs e)
{
...execute the "Add" action here...
}
}
请注意构造函数中的命令绑定以及处理该命令的相应方法。只是为了避免混淆:作为 CommandTarget 的对象不需要实现命令绑定。 CommandTarget 仅指定路由开始的可视/逻辑树中的对象。
虽然我没有在此处显示,但 UserControl 中 DeleteCommand 的实现遵循相同的模式。
public partial class UserControl1 : UserControl
{
public static readonly RoutedCommand DeleteCommand = new RoutedCommand("UserControl1Command.Delete", typeof(UserControl1));
...same implementation approach as demonstrated for CRUDDataGrid1.AddCommand...
}
创建 ToolBar 现在可以完全在 UserControl1.xaml 中进行,而不必担心如何执行命令所代表的相应操作。请注意,使用&lt; Button&gt; 是可以的,因为工具栏完全是在UserControl1中创建的,而这些按钮中的任何一个都不可能被共享&#34;与另一个控制。另外,请注意缺少那些辅助类,例如 UseCommand 和 MergeCollectionsConverter ,这些是我的答案第2部分中有些复杂的场景所必需的。
<UserControl x:Class="UserControl1" ...>
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="Add" Command="{x:Static local:CRUDDataGrid1.AddCommand}" CommandTarget="{Binding ElementName=cdg}" />
<Button Content="Delete" Command="{x:Static local:UserControl1.DeleteCommand}" />
</ToolBar>
</ToolBarTray>
<local:CRUDDataGrid1 x:Name="cdg" ItemsSource="{Binding Customers}">
<local:CRUDDataGrid1.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding XPath=@FirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding XPath=@LastName}" />
</local:CRUDDataGrid1.Columns>
</local:CRUDDataGrid1>
</DockPanel>
</UserControl>
CRUDataGrid1 应该直接从DataGrid类型继承(不是 UserControl ),根据需要实现扩展的CRUD功能。
通过让 CRUDataGrid1 仅为任何所需的用户操作提供 RoutedCommands ,您和团队中的任何其他人都可以自由决定在GUI中使用< em> RoutedCommands - 在工具栏,菜单或其他方面。您可以使用相同的命令使用多个按钮 - 没有问题。 RoutedCommands 背后的基础设施也将根据绑定到命令的 CanExecute 方法的结果自动启用/禁用此类按钮。
在这里给出的示例中,我确实让 CRUDataGrid1 和 UserControl1 提供 RoutedCommands 。但是,如果你有许多命令和更复杂的软件,那么没有什么可以反对定义这些命令的中心位置(类似于Microsoft对.NET框架提供的 RoutedCommands 所做的那样)。 / p>