#c# #wpf #mvvm
#c# #wpf #mvvm
Вопрос:
У меня есть несколько групп наблюдаемых коллекций элементов, которые обновляются в фоновом потоке с регулярным интервалом (от 30 до 60 секунд). Эти коллекции отображаются через ItemsControls в представлении. Родительский элемент и элементы в элементе управления имеют несколько атрибутов отображения, привязанных к статусу каждого элемента.
- Статус будет определять, какая фигура будет отображаться рядом с текстом, а также цвет обводки и заливки этой фигуры.
- Статус будет определять цвет фона и цвет текста текста для этого элемента.
- Статус будет определять, отображается ли таймер обратного отсчета в элементе (таймер не привязан к viewmodel)
- Статус может определять цвет границы родительского контейнера.
В настоящее время я выполняю эту логику в отдельных IValueConverters для каждого свойства. Это работает, но кажется громоздким и разбросанным. Я почти хочу каким-то образом подписаться на событие PropertyChanged в пользовательском интерфейсе и заставить его вызвать один метод для отображения всего отображения для этого элемента, чтобы вся логика содержалась в одном месте. Есть ли лучший способ сделать это или я должен просто придерживаться IValueConverters?
Вот пример того, что у меня есть.
Коллекция:
public ObservableCollection<PanelItem> PanelItems1
{
get { return panelItems1; }
set
{
panelItems1 = value;
base.OnPropertyChanged("PanelItems1");
}
}
PanelItem — это небольшая коллекция свойств, которые включают в себя: имя, значение (статус), Описание. ItemsControls аналогичен следующему:
<Border Grid.Row="0" Grid.Column="0"
BorderBrush="{Binding Path=PanelGroup1.HighestStatus, Converter={StaticResource ParentBorderColorConverter}}"
BorderThickness="3" Height="Auto" Width="Auto" Margin="0,0,2,0">
<StackPanel Grid.Column="0" Grid.Row="0">
<Label Grid.Row="0" Grid.Column="0" Content="First Group" Style="{StaticResource panelTitle}" />
<ItemsControl ItemsSource="{Binding Path=PanelItems1, Mode=TwoWay}"
ItemTemplate="{StaticResource PanelItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!-- Here is the Data Template -->
<DataTemplate x:Key="PanelItemTemplate">
<Viewbox MaxHeight="20" HorizontalAlignment="Left" Cursor="Hand">
<WrapPanel>
<Path Margin="2,2,2,2" StrokeThickness="2">
<Path.Data>
<Binding Path="Status" Mode="OneWay" Converter="{StaticResource ShapeConverter}" />
</Path.Data>
<Path.Fill>
<Binding Path="Status" Mode="OneWay" Converter="{StaticResource ShapeColorConverter}" />
</Path.Fill>
<Path.Stroke>
<Binding Path="Status" Mode="OneWay" Converter="{StaticResource ShapeBorderConverter}" />
</Path.Stroke>
</Path>
<ContentPresenter Content="{Binding Path=Name}" Margin="5,0,0,0" />
</WrapPanel>
</Viewbox>
</DataTemplate>
</StackPanel>
</Border>
Комментарии:
1. Рассматривали ли вы возможность использования диспетчера визуальных состояний? Вы можете создать свой собственный шаблон управления с помощью VSM, создавая резервные копии любых визуальных состояний — очевидно, ему нужно знать, к чему он привязан, чтобы он мог применять эти состояния условно — вы также можете просто использовать триггеры вместо преобразователей значений
2. Вместо того, чтобы предоставлять один статус и применять 3 разных конвертера, вы можете предоставить 3 разных свойства на своей виртуальной машине и выполнить 3 преобразования, чтобы присвоить им значение в виртуальной машине (или в логике, вызываемой виртуальной машиной)
3. Проверьте здесь информацию о VSM blogs.msdn.com/b/wpfsdk/archive/2009/02/27 / … — существует также соответствующая информация о триггерах — я всегда рассматриваю преобразователи значений как способ преобразования значений, а не для запуска визуального состояния, поэтому я стараюсь избегать их на чем-либо громоздком, поскольку они в конечном итоге являются кодом, а не разметкой
4. @Charleh Я этого раньше не видел, очень интересно. Я читаю об этом прямо сейчас. Похоже, что он больше используется для событий взаимодействия с пользовательским интерфейсом, но я мог бы привязать его к свойству viewmodel. Похоже, мне придется добавить VSM для каждого элемента управления, а затем переключать визуальные состояния на основе представления.
5. @stijn Когда вы говорите, что выставляете 3 свойства на виртуальной машине, будут ли эти свойства иметь цвет? Если это так, это как бы помещает информацию о представлении в ViewModel.
Ответ №1:
Почему бы вам просто не объявить обычный класс, который реализует INotifyPropertyChanged
интерфейс для вашего Status
объекта? Просто добавьте в него необходимые свойства, такие как Geometry
, Fill
и Stroke
и т. Д. Если бы вы это сделали, вам не понадобились бы никакие Converter
классы, и вы могли бы сделать что-то вроде этого:
<DataTemplate x:Key="PanelItemTemplate">
<Viewbox MaxHeight="20" HorizontalAlignment="Left" Cursor="Hand" >
<WrapPanel>
<Path Margin="2,2,2,2" StrokeThickness="2" >
<Path.Data>
<Binding Path="Status.Geometry" Mode="OneWay" />
</Path.Data>
<Path.Fill>
<Binding Path="Status.Fill" Mode="OneWay" />
</Path.Fill>
<Path.Stroke>
<Binding Path="Status.Stroke" Mode="OneWay" />
</Path.Stroke>
</Path>
<ContentPresenter Content="{Binding Path=Name}" Margin="5,0,0,0" />
</WrapPanel>
</Viewbox>
</DataTemplate>
Комментарии:
1. Имейте в виду, что свойства, подобные
Fill
иStroke
представляющие визуальное состояние, не должны быть частью вашей viewmodel, если вы намерены правильно следовать шаблону MVVM.2. «должен» не означает «должен»
3. Мне нравится идея, я могу создать класс, который охватывает эту логику. Мой вопрос в том, где он подходит? DataContext представления устанавливается в мою ViewModel, ItemsControl привязан к коллекции элементов в этой ViewModel, как мне вставить новый класс состояния между ними?
4. Я согласен с Adi
Fill
иStroke
не должен быть частью вашей виртуальной машины, поскольку представление должно определять визуальное состояние на основе содержимого виртуальной машины. Вы можете нарушить правило, но если вы передаете представления дизайнеру UX / UI, и им приходится возиться с viewmodels, чтобы изменить визуальное состояние, это как бы сводит на нет всю цель MVVM. Есть простые способы заставить визуальное состояние реагировать на свойства viewmodel с помощью триггеров или VSM. Вы можете быть «полустрогими» с MVVM, но если вы можете найти способ сделать это без «обмана», тогда вы учитесь и обычно создаете более надежное решение.5. Однако — в противовес — если ваша виртуальная машина зависит от конкретного приложения и переносит определенный элемент данных, то вы можете утверждать, что он идет рука об руку с представлением. Если вы повторно используете viewmodels, то, скорее всего, это неправильный подход
Ответ №2:
Можно создать только один конвертер, который будет включать в себя всю логику преобразования и возвращать новый класс, к которому вы будете привязываться:
public class MyNewConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Perform all required conversions here and return them as a new type of object
return new
{
Data = ...,
Fill = ...,
Stroke = ...
};
}
}
Тогда все, что вам нужно сделать, это изменить ваше Path
DataContext
свойство, чтобы использовать этот конвертер.
<Viewbox MaxHeight="20" HorizontalAlignment="Left" Cursor="Hand">
<WrapPanel>
<Path Margin="2,2,2,2" StrokeThickness="2" DataContext="{Binding Path=Status, Converter={StaticResource MyNewConverter}}">
<Path.Data>
<Binding Path="Data" Mode="OneWay" />
</Path.Data>
<Path.Fill>
<Binding Path="Fill" Mode="OneWay" />
</Path.Fill>
<Path.Stroke>
<Binding Path="Stroke" Mode="OneWay" />
</Path.Stroke>
</Path>
<ContentPresenter Content="{Binding Path=Name}" Margin="5,0,0,0" />
</WrapPanel>
</Viewbox>
Комментарии:
1. Мне очень нравится эта идея! Единственная загвоздка, с которой я только что столкнулся, — это некоторые дополнительные требования, которые требуют мигания цвета фона (может быть, раскадровки?) и звукового уведомления о определенных состояниях. Я не уверен, как запустить эти типы событий пользовательского интерфейса… Хм, разве что, возможно, раскадровку можно отключить и включить с помощью привязываемого атрибута на раскадровке? Я сейчас изучаю это.
2. Некоторое время я пользовался этим решением, пока кое-что не обнаружил. Возврат нового анонимного типа, подобного этому, возвращает несколько ошибок привязки. Это действительно работает, но ошибки трудно объяснить менеджеру сборки и влияют на производительность. В конце концов мне пришлось отказаться от этого подхода.
3. Я использовал анонимный тип для простоты. Вы могли бы создать новый класс для этого и избежать этих ошибок.