Захват данных пользовательского элемента управления WPF в свойстве при использовании в качестве шаблона

#c# #wpf #user-controls

#c# #wpf #пользовательские элементы управления

Вопрос:

Я столкнулся с проблемой в своих исследованиях. Я относительно новичок в WPF и любой из его практик, возможно, я пытаюсь сделать что-то, чего не должен … во всяком случае, я не могу найти решение своей проблемы, поэтому я спрошу.

Я создал простое приложение WPF для работы в качестве окна уведомлений. Отображаемое окно содержит ListBox эти уведомления. Я решил использовать a 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 объекта при использовании этого шаблона привязки. Я хочу передать note объект через мой CompleteClicked обработчик событий, чтобы у меня была правильная ссылка на завершаемое уведомление. Но я не знаю, как зафиксировать NotificationObject момент создания уведомления.

Кто-нибудь знает, как захватить привязываемый объект данных и сохранить его в параметре?

Редактировать

Из-за моей личной путаницы я добавляю этот раздел, чтобы внести ясность в свои намерения. То, что я пытаюсь выполнить, — это приложение для настольного трея, которое периодически извлекается из пользовательского веб-приложения для управления задачами и уведомляет пользователей о статусе назначенных им задач в течение конечного периода времени.

Часть приложения tray в моем приложении работает нормально, однако в нем я создал событие, которое выполняется по таймеру, которое обрабатывается как:

 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 и обновить веб-приложение. как я могу подойти к этому?

Комментарии:

1. не могли бы избиратели, проголосовавшие против, пояснить, почему они проголосовали как таковые? Я что-то не так делаю с WPF?

Ответ №1:

Это совсем не то, как вы бы это сделали. «Правильный путь» немного странный, но на самом деле он менее раздражает, как только вы справитесь с ним.

Некоторые из них, возможно, уже выполняются. Дайте мне знать, если вам понадобится более подробная информация по какому-либо конкретному пункту.

  1. Вам нужна основная viewmodel, которая реализует INotifyPropertyChanged .
  2. MainViewModel предоставляет общедоступное свойство ObservableCollection<NotificationObject> Notes { get {...} set {...} } , которое возникает PropertyChanged в set .

  3. MainViewModel отвечает за заполнение Notes . Он никогда не должен знать или заботиться о том, кто смотрит Notes . Это выходит за рамки его обязанностей.

  4. Используйте свою viewmodel:

     public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
      
  5. Привяжите свойство viewmodel Notes к ListBox.ItemsSource в главном представлении XAML. Нет необходимости делать это программно. На самом деле, лучше, если вы этого не сделаете. Теперь он будет прослушивать PropertyChanged и обновлять список, если вы назначите новую коллекцию Notes . Тема с MVVM заключается в том, что вы устанавливаете свойства в viewmodel, и в пользовательском интерфейсе происходят волшебные (волшебные, я вам говорю) вещи.

     <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 свойство и вызывает Completed событие, когда IsCompleted становится истинным. В качестве альтернативы, у вас может быть 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 также просто обработать PropertyChanged NotificationObject созданные им файлы. Это дело вкуса; я предпочитаю этот способ.

    Или, возможно, было бы лучше вместо этого просто отфильтровать элементы, отображаемые в списке IsCompleted , в зависимости от ваших требований. Затем MainViewModel будет обновляться фильтр при любом NotificationObject изменении его IsCompleted состояния.

  8. В представлении нет CompletedClicked события и частной note собственности. Вместо этого он просто сообщает Note , что он завершен. Последствия этого не касаются представления. Это все между viewmodels.

     private void completeBtn_Click(object sender, RoutedEventArgs e) {
        ((NotificationObject)DataContext).IsCompleted = true;
    }
      

    Или пропустите событие щелчка и используйте кнопку переключения:

     <ToggleButton IsChecked="{Binding IsCompleted}" Content="Complete" />
      

    Когда пользователь нажимает на него, оно принимает значение IsCompleted true. Выполнено. Когда вы обнаруживаете, что ваш codebehind сжимается до всего лишь конструктора, или когда вы обнаруживаете, что ваш usercontrol может быть просто равниной DataTemplate без кода, это признак того, что вы правильно используете MVVM.

Вы, вероятно, читаете это, и ваши глаза вращаются против часовой стрелки. Если это так, это понятно. Вот в чем дело: на самом деле это не так уж плохо, как только вы к этому привыкнете, и это ставит вас в такое положение, когда вы просто настраиваете кучу вещей, связываете их вместе декларативно, и по большей части это просто работает.

Комментарии:

1. фух … ты прав насчет того, что у меня закружились глаза…. Поскольку я пытался заставить это работать, я не вижу, как я заполняю новую коллекцию заметок… как мне получить к этому доступ?

2. Вероятно, вы бы заполнили его в MainViewModel конструкторе. Откуда берутся заметки? Правило с viewmodels заключается в том, что viewmodels — это, так сказать, программа: они владеют данными, они общаются друг с другом. Представления — это просто своего рода украшения, развешанные на ветвях, отображающие материал для пользователя и получающие ввод.

3. Хорошо, позвольте мне уточнить мой вопрос. На разных этапах жизненного цикла приложений этот список заметок будет меняться. как мне затем изменить коллекцию заметок в точке после инициализации? Или я должен создавать новый экземпляр этого окна каждый раз, когда оно отображается?

4. Вы изменяете коллекцию заметок, добавляя и удаляя элементы. ObservableCollection Автоматически сообщает ListBox , что произошло, и ListBox волшебным образом обновляется. Это просто работает. Если вы INotifyPropertyChanged правильно реализуете MainViewModel , MainViewModel можете просто сказать Notes = new ObservableCollection<NotificationObject>(); , и ListBox в этом случае они волшебным образом будут повторно заполнены.

5. Хорошо, мы приближаемся к моему ответу. Разрыв, который у меня возникает, происходит не между Notes и ListBox , а скорее между каким-то скрытым событием, которое собирает новый список объектов для отображения и обновления Notes с помощью этого нового списка. Notes Сидя на DataContext том, как мне затем получить к нему доступ? что-то вроде DataContext.Notes не работает….