Как создать представление на основе страницы для коллекции пользовательских элементов управления в C# WPF? - PullRequest
0 голосов
/ 05 апреля 2020

Предисловие: Трудно объяснить мою проблему в форме вопроса, поэтому позвольте мне объяснить ниже.

Контекст: я разрабатываю пользовательский интерфейс для звукового микшера (см. Рисунок) и как часть из этого у меня есть ряд из 16 «Channel Strips» (UserControl) каждая с фейдером. В модели же микшер имеет 32 канала (+ вспомогательные). Чтобы устранить необходимость в очень большом ItemsControl с полосой прокрутки, я хочу реализовать систему страниц для переключения между каналами в модели, с которыми связан пользовательский интерфейс.

Mock up of the UI. Макет интерфейса пользователя.

После некоторого прочтения примеров архитектуры MVVM я сузил его до слишком многих способов реализовать это, но у меня есть проблемы с каждым из них.

  1. Привязка канальных полос в пользовательский интерфейс непосредственно к ObservableCollection в ViewModel, а затем с помощью уведомителей о свойствах связать это с данными в модели:
<ItemsControl x:Name="FaderPane1_8" Background="{DynamicResource FaderPanel}" Margin="0" ItemsSource="{Binding Faders}" ScrollViewer.CanContentScroll="False" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Disabled">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:ChannelStrip MaxWidth="50" FaderValue="0"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

MainWindow.xaml

 public ObservableCollection<ChannelStrip> Faders
 {
    get { return _faders; }
    set { SetProperty(ref _faders, value); }
 }

 public void Init()
 {
    //Create a new mixer
    VMixer = new Mixer(32, 8, 8);

    VMixer.MixChannels.CollectionChanged += MixChannels_CollectionChanged;
 }
 //More code to handle "MixChannels_CollectionChanged" as well as changes from ViewModel to Model

ViewModel. cs

Проблема в том, что это создает множество обработчиков событий для изменений свойств во всех направлениях, а также в результате копирует фейдеры в память. Все это кажется мне плохим дизайном, но это то, что я видел, продиктовано соглашением, потому что оно позволяет полностью отделить модель от ViewModel и View, а также не имеет прямой связи между View и Model.

Привязка непосредственно к части модели и использование ViewModel для повторной привязки полосок канала пользовательского интерфейса к другому набору каналов при каждом изменении страницы.
Это кажется более разумным, поскольку я не создавал ненужных копий данных и событий, и может привести к менее сложному коду.
<Grid x:Name="FaderPane9_16" Background="{DynamicResource FaderPanel}" Margin="0" Grid.Column="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <local:ChannelStrip/>
                <local:ChannelStrip Grid.Column="1"/>
                <local:ChannelStrip Grid.Column="2"/>
                <local:ChannelStrip Grid.Column="3"/>
                <local:ChannelStrip Grid.Column="4"/>
                <local:ChannelStrip Grid.Column="5"/>
                <local:ChannelStrip Grid.Column="6"/>
                <local:ChannelStrip Grid.Column="7"/>
            </Grid>

MainWindow.xaml

 public void UpdateFaderBindings(ChannelType faderMode, int faderModePage)
 {
     //[Code omitted for simplicity]

     for (int i = 0; i < channels; i++)
     {
         int newFaderIndex = -1;
         //[Code omitted for simplicity]

         Binding b = new Binding("VMixer.Channels[newFaderIndex].FaderValue");//I know this is wrong I'm trying to demonstrate the idea
         ChannelStrips[i].SetBinding(ChannelStrip.FaderValue, b);
     }
 }

ViewModel.cs

Обе эти архитектуры у них явно есть свои недостатки, и после нескольких часов чтения я не могу решить, какова правильная архитектура в этой ситуации.

Ответы [ 2 ]

1 голос
/ 06 апреля 2020

Я пишу системы регистрации в аэропорту WPF, и постоянно сталкиваюсь с этой проблемой в виде выбора места в самолете. Недостаточно места для отображения всех 525 посадочных мест A380, поэтому я должен отобразить секции фюзеляжа и позволить пассажирам просматривать их. Аналогичным образом, в киоске регистрации может потребоваться представить 50 различных авиакомпаний для выбора пассажира, поэтому для их прокрутки снова используется пейджинг.

Что вы действительно пытаетесь сделать, так это реализовать форму виртуализации. Что, в свою очередь, требует просмотра лога c. И всякий раз, когда у вас есть логика вида c, которая не требует прямого взаимодействия с фактическим элементом GUI, правильное место для его размещения почти всегда находится на уровне модели представления. В коммерческом приложении это абсолютно поведение, которое вы хотели бы выполнить модульным тестом, но с опцией # 2 вы не можете сделать это без присутствия элементов GUI.

Причина, по которой опция # 1 выглядит грязной, заключается в том, что потому что это так, несмотря на то, что ближе к чистому MVVM. То, что вы действительно должны сделать, это создать соотношение 1: 1 между вашими представлениями и вашими моделями представлений. Я лично создал бы класс MixerViewModel для каждого из ваших микшеров (видимый или нет), содержащий информацию, необходимую только этому микшеру, а также вел бы список тех, которые в данный момент видны:

private IList<MixerViewModel> AllMixers;
public ObserveableCollection<MixerViewModel> VisibleMixers {get; set;} // would probably also need INPC

Первый список предназначен для всех 32 ваших микшеров и создается при запуске. Этот второй список является тем, что видно в данный момент, вы заполняете его элементами из первого списка при каждом изменении текущей страницы. Это облегчает полное разделение задач, позволяет очень легко изменить общее количество элементов (либо общее, либо видимое сразу), и теперь у вас также есть возможность провести юнит-тестирование. Да, это означает, что GUI элементы создаются и уничтожаются при каждом изменении страницы, но именно так WPF был разработан для использования и до тех пор, пока вы не go за бортом, ваше приложение будет реагировать.

0 голосов
/ 05 апреля 2020

Определенно, первое решение более подходит, но я бы, возможно, дополнительно попытался бы избежать всех обработчиков, просто создав 2 отдельных элемента ItemsControls в пользовательском интерфейсе (привязанных к одной и той же ObservableCollection) друг над другом со вторым скрытым и с кнопками, которые изменяют каналы. просто верните видимость обоих ItemsControls.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...