当用作模板时,在属性中捕获WPF用户控件'
本文关键字:WPF 用户 控件 属性 | 更新日期: 2023-09-27 18:12:31
我的研究遇到了一点麻烦。我对WPF和它的任何实践都比较陌生,也许我正在尝试一些我不应该做的事情……无论如何,我找不到解决问题的办法,所以我要问。
我创建了一个简单的WPF应用程序作为通知框。显示的窗口有一个包含这些通知的ListBox
。我决定使用user-control
来模板这些通知,并为每个通知添加所需的功能。例如,完成、确认或忽略的能力。
我已经能够让这些通知显示得很好,但是我的问题是创建一个适当的绑定来访问整个自定义NotificationObject
的道路(因为它是处理该逻辑的应用程序,而不是用户控制本身)。
我的资料如下。
窗口的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应用程序中提取,并在有限的时间框架内通知用户分配给他们的给定任务的状态。
我的应用程序的托盘应用程序部分运行良好,但在它我已经创建了一个事件,运行在一个定时器处理为:
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
列表框中的通知并更新web应用程序。我该怎么做呢?
你根本就不会这么做。"正确的方式"有点奇怪,但一旦你掌握了它,它实际上就不那么烦人了。
其中一些,它可能已经在做了。如果你需要任何具体的细节,请告诉我。- 你需要一个实现
INotifyPropertyChanged
的主视图模型。 MainViewModel
公开了一个公共属性ObservableCollection<NotificationObject> Notes { get {...} set {...} }
,它在set
. c中引发PropertyChanged
。MainViewModel
负责填充Notes
。它不应该知道或关心谁在看Notes
。那超出了它的职责范围。使用viewmodel:
public MainWindow() { InitializeComponent(); DataContext = new MainViewModel(); }
将视图模型的
Notes
属性绑定到主视图XAML中的ListBox.ItemsSource
。不需要用编程的方式来做。事实上,如果你不这样做会更好。现在,如果给Notes
分配一个新的集合,它将监听PropertyChanged
并更新列表框。MVVM的主题是你在视图模型上设置属性,然后事情神奇地(神奇地,我告诉你)发生在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
变为true时引发Completed
事件。或者,您可以在IsCompleted
从false变为true时触发IsCompletedChanged
事件,反之亦然。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
s上处理PropertyChanged
。这是个品位问题;我更喜欢这种方式。或者,根据您的需求,简单地通过
IsCompleted
过滤ListBox中显示的项目可能会更好。然后,当任何NotificationObject
改变其IsCompleted
状态时,MainViewModel
将刷新过滤器。在视图中,没有
CompletedClicked
事件,也没有私有note
属性。相反,它只是告诉它的Note
它已经完成了。这样做的后果与视图无关。这些都在视图模型之间。private void completeBtn_Click(object sender, RoutedEventArgs e) { ((NotificationObject)DataContext).IsCompleted = true; }
或者跳过click事件并使用切换按钮:
<ToggleButton IsChecked="{Binding IsCompleted}" Content="Complete" />
当用户单击它时,它将
IsCompleted
设置为true。完成了。当你发现你的代码隐藏缩小到只有一个构造函数,或者当你发现你的usercontrol可能只是一个没有代码的普通DataTemplate
,这是你做MVVM正确的标志。
你可能正在读这篇文章,你的眼睛正在逆时针旋转。如果是这样,那是可以理解的。事情是这样的:一旦你习惯了它,它实际上并没有那么糟糕,它把你放在一个地方,你只需要设置一堆东西,声明性地将它们链接在一起,并且在大多数情况下它只是工作。