Загрузка XAML во время выполнения с использованием шаблона MVVM в WPF - PullRequest
3 голосов
/ 26 января 2012

Это вопрос, который выходит за рамки первоначально размещенного здесь: Ссылка на загрузку-xaml во время выполнения

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

  1. My View объявляет экземпляр ViewModel как ресурс и создает его экземпляр.ViewModel
  2. В моем конструкторе ViewModel я загружаю свойство XamlString, поступающее из внешнего источника (файл или дБ ..)
  3. На мой взгляд, у меня есть кнопка, которую пользователь нажимает после завершения загрузки ViewModelи в коде позади события щелчка я десериализирую динамически загруженный XAML и добавляю его в свою сетку.

Мой вопрос заключается в том, как я могу устранить кодовый код и автоматизировать логику, чтобыView может динамически отображать новый раздел xaml сразу после того, как ViewModel завершит получение содержимого XAML и инициализацию свойства string?

SДолжен ли я использовать какую-либо шину обмена сообщениями, чтобы ViewModel уведомлял, как только свойство было установлено, чтобы представление могло добавить новый контент?

Меня беспокоит тот факт, что ViewModels имеют ссылку на Views и не должны отвечать за создание элементов пользовательского интерфейса.

Заранее спасибо!

Редактировать : Просто для пояснения: в моем конкретном случае я не пытаюсь привязать бизнес-объект или коллекцию (модель) к элементу пользовательского интерфейса (например, Grid), что, очевидно, может быть выполнено с помощью шаблонов и привязки.Моя ViewModel извлекает всю форму XAML из внешнего источника и задает ее как строковое свойство, доступное для представления.

Мой вопрос: кто должен отвечать за десериализацию этого строкового свойства XAML в элемент пользовательского интерфейса идобавить его программно в мою сетку, как только будет установлено свойство строки Xaml в виртуальной машине?
Для меня это больше похоже на ответственность View, а не ViewModel.Но шаблон, насколько я понимаю, заставляет заменять любую логику кода позади V-VM.

Ответы [ 2 ]

5 голосов
/ 27 января 2012

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

У меня есть упрощенная ViewModel:

public class MyViewModel : ViewModelBase
{
   //This property implements INPC and triggers notification on Set
   public string XamlViewData {get;set;}

   public ViewModel()
   {
      GetXamlFormData();  
   }

   //Gets the XAML Form from an external source (e.g. Database, File System)
   public void GetXamlFormData()
   {
       //Set the Xaml String property
       XamlViewData = //Logic to get XAML string from external source
   }
}

Теперь мой вид:

<UserControl.Resources>
<ViewModel:MyViewModel x:Key="Model"></ViewModel:MyViewModel>
</UserControl.Resources>
<Grid DataContext="{StaticResource Model}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <StackPanel>
    <!-- This is the Grid used as a Place Holder to populate the dynamic content!-->
    <Grid x:Name="content" Grid.Row="1" Margin="2"/>
    <!-- Then create a Hidden TextBlock bound to my XamlString property. Right after binding happens I will trigger an event handled in the code-behind -->
    <TextBlock Name="tb_XamlString" Text="{Binding Path=XamlViewData, Mode=TwoWay, UpdateSourceTrigger=LostFocus, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Visibility="Hidden" Loaded="tb_XamlString_Loaded" />
    </StackPanel>
</Grid>

По сути, я создал скрытый TextBlock, связанный с моим свойством XAML String в ViewModel, и подключил его событие Loaded к обработчику событий в коде позади View:

    private void tb_XamlString_Loaded(object sender, RoutedEventArgs routedEventArgs)
    {
        //First get the ViewModel from DataContext
        MyViewModel vm = content.DataContext as MyViewModel;
        FrameworkElement rootObject = XamlReader.Parse(vm.XamlViewData) as FrameworkElement;
        //Add the XAML portion to the Grid content to render the XAML form dynamically!
        content.Children.Add(rootObject);
    }

Это может быть не самым элегантным, но выполняет свою работу. Как говорят некоторые люди, в MVVM есть такие случаи, когда требуется немного кода с выделенным кодом. Это не повредит, и часть этого решения все еще использует принципы привязки V-VM при использовании виртуальной машины для извлечения и заполнения свойства XamlString и предоставления его представлению. Если мы хотим выполнить модульное тестирование функциональности синтаксического анализа и загрузки XAML, мы можем делегировать его в отдельный класс.

Я надеюсь, что кто-то найдет это полезным!

2 голосов
/ 26 января 2012

Мне трудно понять, что вы говорите, поэтому мой ответ будет основан на моей интерпретации. Вы должны рассмотреть возможность размещения образца (упрощенного) того, что вы пытаетесь сделать.

1) Я думаю, вы не понимаете, что делает MVVM. MVVM - это в основном шаблон на основе привязки . Ваша модель представления должна предоставлять свойства, содержащие бизнес-объекты, и ваше представление должно просто привязываться к этим свойствам. Если я вас неправильно понимаю, и это то, что вы делаете, то ваша проблема в том, что ваше представление должно знать, когда обновляются свойства (после десериализации вашего xaml и т. Д.). Есть два способа сделать это: INotifyPropertyChanged интерфейс вашей модели представления, или заставить вашу модель представления наследоваться от DependencyObject, и установить свойства зависимостей свойств. Я не буду вдаваться в подробности, потому что это большая тема, которую вы должны изучить в Google, прежде чем принимать решение.

2) Вообще говоря, вы не должны использовать события щелчка внутри своего представления, если вы используете MVVM. Вместо этого создайте свойства для модели представления типа ICommand (и создайте соответствующие реализации ICommand или используйте реализацию DelegateCommand (google it), которая позволит вам использовать делегатов для реализации интерфейса. Идея заключается в том, что ваш view привязывается к свойству и выполняет обработчик непосредственно внутри viewmodel.

3) Если вы хотите передать информацию из модели представления в представление, то вы должны создать событие в модели представления и подписаться на него в представлении, но это последнее средство, которое будет использоваться только в случаях, например при отображении новое окно и т. д. Как правило, вы должны использовать привязку.

4) Чтобы быть более точным в том, что вы делаете, вы должны привязать свойство ItemsSource вашей Grid к какому-либо свойству модели представления. Обратите внимание, что свойство модели представления должно иметь тип ObservableCollection<T>, если вы хотите иметь возможность добавлять элементы и получать мгновенные обновления.

Надеюсь, это поможет.

...