用作模板时在 属性 中捕获 WPF 用户控件的数据
Capturing a WPF user control's data in a property when used as a template
我的研究 运行 遇到了麻烦。我对 WPF 及其任何实践都比较陌生,也许我正在尝试一些我不应该做的事情......无论如何,我找不到解决我的问题的方法所以我会问。
我创建了一个简单的 WPF 应用程序来充当通知框。显示的 window 有一个包含这些通知的 ListBox
。我决定使用 user-control
来模板化这些通知并为每个通知添加所需的功能。例如,完成、确认或忽略的能力。
我已经能够让这些通知显示得很好,但是我的问题是创建一个正确的绑定来访问整个自定义 NotificationObject
的道路(因为它是处理的应用程序该逻辑与用户控件本身相反)。
我的情况如下
window 有一个 ListBox
定义如下:
<ListBox Name="NotificaitonContainer" ItemTemplate="{StaticResource NotificationTemplate}"/>
ItemSource
在后端设置为:NotificaitonContainer.ItemsSource = notificationList;
模板定义为:
<DataTemplate x:Key="NotificationTemplate">
<StackPanel Orientation="Horizontal" Margin="5,5,5,5">
<uc:Notification CompleteClicked="CompleteTask" />
</StackPanel>
</DataTemplate>
user-control
定义为:
<UserControl x:Class="Notification_WPF.Windows.Components.Notification"
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" Height="50" Width="400" Background="#FFACA4A4">
<Grid>
<Label Content="{Binding Path=name}" />
<Button Name="completeBtn" Content="complete" Click="completeBtn_Click"/>
</Grid>
后面的代码定义为:
public partial class Notification : UserControl{
NotificationObject note;
public event EventHandler CompleteClicked;
public Notification() {
InitializeComponent();
}
private void completeBtn_Click(object sender, RoutedEventArgs e) {
((Panel)this.Parent).Children.Remove(this);
if (CompleteClicked != null) {
CompleteClicked(this, EventArgs.Empty);
}
}
}
现在,我遇到的问题是在使用这种绑定模式时设置 note
对象。我希望通过我的 CompleteClicked
事件处理程序传递 note
对象,以便我可以正确引用正在完成的通知。但是我不知道如何在创建通知时捕获 NotificationObject
。
有谁知道如何捕获绑定的数据对象并将其存储在参数上?
编辑
由于我个人的困惑,我添加此部分是为了阐明我的意图。
我想要完成的是一个桌面托盘应用程序,它定期从自定义任务管理 Web 应用程序中提取,并在有限的时间范围内通知用户分配给他们的给定任务的状态。
我的应用程序的托盘应用程序部分 运行 正常,但是我在其中创建了一个事件 运行s 在计时器上处理为:
private void TimerHandeler(object sender, EventArgs e) {
List<NotificationObject> NotifyList = getNotificationList();
if (notificationBox.Visibility == Visibility.Visible) {
notificationBox.Display(NotifyList);
} else {
notificationBox.Dispatcher.Invoke(DispatcherPriority.Render, null);
}
}
notificationBox
本质上是我的 MainWindow
并且包含我正在使用的 ListBox
,如上所示。
为了显示它,我将代码隐藏为:
public void Display(List<NotificationObject> notifications) {
var desktop = SystemParameters.WorkArea;
this.Left = desktop.Right - this.Width - 15;
this.Top = desktop.Bottom - this.Height - 15;
this.Focus();
buildTaskList(notifications);
this.Show();
}
private void buildTaskList(List<NotificationObject> notifications) {
//this is how I did this before... which is apparently incorrect.
this.notificationList = notifications;
NotificaitonContainer.ItemsSource = this.notificationList;
}
我听说新的 NotificatonList
应该是 MainViewModel
,看起来像:
class NotificationList : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<NotificationObject> _noteList;
public ObservableCollection<NotificationObject> NoteList {
get { return _noteList; }
set {
_noteList = value;
onListChange();
}
}
private void onListChange() {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(null));
}
}
}
我希望我把它们放在一起...
现在我要做的是单击 notificationBox
列表框中的通知并更新网络应用程序。我该如何着手解决这个问题?
你根本不会这样做。 "right way" 有点奇怪,但一旦你掌握了它,它实际上就不那么烦人了。
其中一些,它可能已经在做。如果您需要有关任何特定点的更多详细信息,请告诉我。
- 您需要一个实现
INotifyPropertyChanged
的主视图模型。
MainViewModel
公开了一个 public 属性 ObservableCollection<NotificationObject> Notes { get {...} set {...} }
,它在 set
中引发了 PropertyChanged
。
MainViewModel
负责填充 Notes
。它永远不应该知道或关心谁在看 Notes
。这超出了它的职责范围。
使用您的视图模型:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
将视图模型的 Notes
属性 绑定到主视图 XAML 中的 ListBox.ItemsSource
。无需以编程方式进行。事实上,如果你不这样做会更好。现在它将监听 PropertyChanged
并在您将新集合分配给 Notes
时更新列表框。 MVVM 的主题是你在 viewmodel 上设置属性,事情神奇地发生在 UI 中(我告诉你 神奇地 ,我告诉你)。
<ListBox
ItemsSource="{Binding Notes}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<!--
Notification no longer has the CompleteClicked event.
Notification.DataContext will be the NotificationObject for this
ListBox item. You're already using that fact to bind the name property.
-->
<uc:Notification />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
NotificationObject
也应该实现 INotifyPropertyChanged
。
NotificationObject
获得 IsCompleted
属性,并在 IsCompleted
变为真时引发 Completed
事件。或者,您可以有一个 IsCompletedChanged
事件,该事件在 IsCompleted
从 false 变为 true 时引发,反之亦然。
private bool _isCompleted = false;
public bool IsCompleted {
get { return _isCompleted; }
set {
if (_isCompleted != value)
{
_isCompleted = value;
if (_isCompleted)
OnCompleted();
OnPropertyChanged();
}
}
}
// Purists may argue that an event handler's "args" parameter should always
// inherit from EventArgs, and they may be right, but it's no capital crime.
public event EventHandler<NotificationObject> Completed;
private void OnCompleted() {
if (Completed != null) {
Completed(this, this);
}
}
MainViewModel
的处理程序:
void note_Completed(object sender, NotificationObject note)
{
Notes.Remove(note);
}
它在创建 NotificationObject
个实例并用它们填充 Notes
时设置它。相反,您可能会丢失此事件,并且 MainViewModel
也可以只处理它创建的 NotificationObject
上的 PropertyChanged
。这是一个品味问题;我更喜欢这种方式。
或者,根据您的要求,通过 IsCompleted
简单地过滤列表框中显示的项目可能会更好。然后 MainViewModel
将在任何 NotificationObject
更改其 IsCompleted
状态时刷新过滤器。
在视图中,没有 CompletedClicked
事件,也没有私人 note
属性。相反,它只是告诉它的 Note
它已经完成了。其后果是 none 视图的业务。这就是视图模型之间的全部内容。
private void completeBtn_Click(object sender, RoutedEventArgs e) {
((NotificationObject)DataContext).IsCompleted = true;
}
或者跳过点击事件并使用切换按钮:
<ToggleButton IsChecked="{Binding IsCompleted}" Content="Complete" />
当用户点击它时,它会将 IsCompleted
设置为 true。完毕。当您发现您的代码隐藏只剩下一个构造函数时,或者当您发现您的用户控件可能只是一个没有代码的普通 DataTemplate
时,这表明您正在正确执行 MVVM。
您可能正在阅读本文并且您的眼睛在逆时针旋转。如果是这样,那是可以理解的。事情是这样的:一旦你习惯了它实际上并没有那么糟糕,它会让你处于一个你只需设置一堆东西的地方,link 以声明的方式将它们放在一起,并且在大多数情况下 它只是工作。
我的研究 运行 遇到了麻烦。我对 WPF 及其任何实践都比较陌生,也许我正在尝试一些我不应该做的事情......无论如何,我找不到解决我的问题的方法所以我会问。
我创建了一个简单的 WPF 应用程序来充当通知框。显示的 window 有一个包含这些通知的 ListBox
。我决定使用 user-control
来模板化这些通知并为每个通知添加所需的功能。例如,完成、确认或忽略的能力。
我已经能够让这些通知显示得很好,但是我的问题是创建一个正确的绑定来访问整个自定义 NotificationObject
的道路(因为它是处理的应用程序该逻辑与用户控件本身相反)。
我的情况如下
window 有一个 ListBox
定义如下:
<ListBox Name="NotificaitonContainer" ItemTemplate="{StaticResource NotificationTemplate}"/>
ItemSource
在后端设置为:NotificaitonContainer.ItemsSource = notificationList;
模板定义为:
<DataTemplate x:Key="NotificationTemplate">
<StackPanel Orientation="Horizontal" Margin="5,5,5,5">
<uc:Notification CompleteClicked="CompleteTask" />
</StackPanel>
</DataTemplate>
user-control
定义为:
<UserControl x:Class="Notification_WPF.Windows.Components.Notification"
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" Height="50" Width="400" Background="#FFACA4A4">
<Grid>
<Label Content="{Binding Path=name}" />
<Button Name="completeBtn" Content="complete" Click="completeBtn_Click"/>
</Grid>
后面的代码定义为:
public partial class Notification : UserControl{
NotificationObject note;
public event EventHandler CompleteClicked;
public Notification() {
InitializeComponent();
}
private void completeBtn_Click(object sender, RoutedEventArgs e) {
((Panel)this.Parent).Children.Remove(this);
if (CompleteClicked != null) {
CompleteClicked(this, EventArgs.Empty);
}
}
}
现在,我遇到的问题是在使用这种绑定模式时设置 note
对象。我希望通过我的 CompleteClicked
事件处理程序传递 note
对象,以便我可以正确引用正在完成的通知。但是我不知道如何在创建通知时捕获 NotificationObject
。
有谁知道如何捕获绑定的数据对象并将其存储在参数上?
编辑
由于我个人的困惑,我添加此部分是为了阐明我的意图。 我想要完成的是一个桌面托盘应用程序,它定期从自定义任务管理 Web 应用程序中提取,并在有限的时间范围内通知用户分配给他们的给定任务的状态。
我的应用程序的托盘应用程序部分 运行 正常,但是我在其中创建了一个事件 运行s 在计时器上处理为:
private void TimerHandeler(object sender, EventArgs e) {
List<NotificationObject> NotifyList = getNotificationList();
if (notificationBox.Visibility == Visibility.Visible) {
notificationBox.Display(NotifyList);
} else {
notificationBox.Dispatcher.Invoke(DispatcherPriority.Render, null);
}
}
notificationBox
本质上是我的 MainWindow
并且包含我正在使用的 ListBox
,如上所示。
为了显示它,我将代码隐藏为:
public void Display(List<NotificationObject> notifications) {
var desktop = SystemParameters.WorkArea;
this.Left = desktop.Right - this.Width - 15;
this.Top = desktop.Bottom - this.Height - 15;
this.Focus();
buildTaskList(notifications);
this.Show();
}
private void buildTaskList(List<NotificationObject> notifications) {
//this is how I did this before... which is apparently incorrect.
this.notificationList = notifications;
NotificaitonContainer.ItemsSource = this.notificationList;
}
我听说新的 NotificatonList
应该是 MainViewModel
,看起来像:
class NotificationList : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<NotificationObject> _noteList;
public ObservableCollection<NotificationObject> NoteList {
get { return _noteList; }
set {
_noteList = value;
onListChange();
}
}
private void onListChange() {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(null));
}
}
}
我希望我把它们放在一起...
现在我要做的是单击 notificationBox
列表框中的通知并更新网络应用程序。我该如何着手解决这个问题?
你根本不会这样做。 "right way" 有点奇怪,但一旦你掌握了它,它实际上就不那么烦人了。
其中一些,它可能已经在做。如果您需要有关任何特定点的更多详细信息,请告诉我。
- 您需要一个实现
INotifyPropertyChanged
的主视图模型。 MainViewModel
公开了一个 public 属性ObservableCollection<NotificationObject> Notes { get {...} set {...} }
,它在set
中引发了PropertyChanged
。MainViewModel
负责填充Notes
。它永远不应该知道或关心谁在看Notes
。这超出了它的职责范围。使用您的视图模型:
public MainWindow() { InitializeComponent(); DataContext = new MainViewModel(); }
将视图模型的
Notes
属性 绑定到主视图 XAML 中的ListBox.ItemsSource
。无需以编程方式进行。事实上,如果你不这样做会更好。现在它将监听PropertyChanged
并在您将新集合分配给Notes
时更新列表框。 MVVM 的主题是你在 viewmodel 上设置属性,事情神奇地发生在 UI 中(我告诉你 神奇地 ,我告诉你)。<ListBox ItemsSource="{Binding Notes}" > <ListBox.ItemTemplate> <DataTemplate> <!-- Notification no longer has the CompleteClicked event. Notification.DataContext will be the NotificationObject for this ListBox item. You're already using that fact to bind the name property. --> <uc:Notification /> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
NotificationObject
也应该实现INotifyPropertyChanged
。NotificationObject
获得IsCompleted
属性,并在IsCompleted
变为真时引发Completed
事件。或者,您可以有一个IsCompletedChanged
事件,该事件在IsCompleted
从 false 变为 true 时引发,反之亦然。private bool _isCompleted = false; public bool IsCompleted { get { return _isCompleted; } set { if (_isCompleted != value) { _isCompleted = value; if (_isCompleted) OnCompleted(); OnPropertyChanged(); } } } // Purists may argue that an event handler's "args" parameter should always // inherit from EventArgs, and they may be right, but it's no capital crime. public event EventHandler<NotificationObject> Completed; private void OnCompleted() { if (Completed != null) { Completed(this, this); } }
MainViewModel
的处理程序:void note_Completed(object sender, NotificationObject note) { Notes.Remove(note); }
它在创建
NotificationObject
个实例并用它们填充Notes
时设置它。相反,您可能会丢失此事件,并且MainViewModel
也可以只处理它创建的NotificationObject
上的PropertyChanged
。这是一个品味问题;我更喜欢这种方式。或者,根据您的要求,通过
IsCompleted
简单地过滤列表框中显示的项目可能会更好。然后MainViewModel
将在任何NotificationObject
更改其IsCompleted
状态时刷新过滤器。在视图中,没有
CompletedClicked
事件,也没有私人note
属性。相反,它只是告诉它的Note
它已经完成了。其后果是 none 视图的业务。这就是视图模型之间的全部内容。private void completeBtn_Click(object sender, RoutedEventArgs e) { ((NotificationObject)DataContext).IsCompleted = true; }
或者跳过点击事件并使用切换按钮:
<ToggleButton IsChecked="{Binding IsCompleted}" Content="Complete" />
当用户点击它时,它会将
IsCompleted
设置为 true。完毕。当您发现您的代码隐藏只剩下一个构造函数时,或者当您发现您的用户控件可能只是一个没有代码的普通DataTemplate
时,这表明您正在正确执行 MVVM。
您可能正在阅读本文并且您的眼睛在逆时针旋转。如果是这样,那是可以理解的。事情是这样的:一旦你习惯了它实际上并没有那么糟糕,它会让你处于一个你只需设置一堆东西的地方,link 以声明的方式将它们放在一起,并且在大多数情况下 它只是工作。