#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:
Это совсем не то, как вы бы это сделали. «Правильный путь» немного странный, но на самом деле он менее раздражает, как только вы справитесь с ним.
Некоторые из них, возможно, уже выполняются. Дайте мне знать, если вам понадобится более подробная информация по какому-либо конкретному пункту.
- Вам нужна основная viewmodel, которая реализует
INotifyPropertyChanged
. -
MainViewModel
предоставляет общедоступное свойствоObservableCollection<NotificationObject> Notes { get {...} set {...} }
, которое возникаетPropertyChanged
вset
. -
MainViewModel
отвечает за заполнениеNotes
. Он никогда не должен знать или заботиться о том, кто смотритNotes
. Это выходит за рамки его обязанностей. -
Используйте свою viewmodel:
public MainWindow() { InitializeComponent(); DataContext = new MainViewModel(); }
-
Привяжите свойство 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>
-
NotificationObject
INotifyPropertyChanged
также должно быть реализовано. -
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
состояния. -
В представлении нет
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
не работает….