用作模板时在 属性 中捕获 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" 有点奇怪,但一旦你掌握了它,它实际上就不那么烦人了。

其中一些,它可能已经在做。如果您需要有关任何特定点的更多详细信息,请告诉我。

  1. 您需要一个实现 INotifyPropertyChanged 的主视图模型。
  2. MainViewModel 公开了一个 public 属性 ObservableCollection<NotificationObject> Notes { get {...} set {...} },它在 set 中引发了 PropertyChanged

  3. MainViewModel 负责填充 Notes。它永远不应该知道或关心谁在看 Notes。这超出了它的职责范围。

  4. 使用您的视图模型:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
    
  5. 将视图模型的 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>
    
  6. NotificationObject 也应该实现 INotifyPropertyChanged

  7. 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 状态时刷新过滤器。

  8. 在视图中,没有 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 以声明的方式将它们放在一起,并且在大多数情况下 它只是工作