0

У меня есть 3 ViewModel: базовая, и 2 дочерних. Хочу при помощи ViewModelLocator иметь возможность управлять свойством, которое находится в базовой ВМ, из дочерних, так чтобы они работали с одним и тем же экземпляром, а не каждый со своим, и соответственно обе реагировали на изменения. Конкретно в базовой ВМ есть bool свойство, которое должно меняться из других ВМ.
Пользовался этой статьей(https://msdn.microsoft.com/en-us/library/hh821028.aspx). Создал в App.xaml экземпляр ViewModelLocator, сам класс должен отдавать экземпляр нужной ВМ, когда я делаю на него Binding на Xaml.

UPD

Locator

public class ViewModelLocator
{
    private BaseViewModel baseVm;
    public BaseViewModel BaseVm
    {
        get { return new BaseViewModel(); }
    }
}

App.xaml

<viewModels:ViewModelLocator x:Key="ViewModelLocator"/>

MainPage.xaml элементы которые забиндены на нужные мне св-ва из BaseViewModel

<Image Visibility="{Binding IsFilterImgVisible, Source={StaticResource ViewModelLocator} ,Converter={StaticResource BooleanToVisibilityConverter}}"/>


<controls:ImageManipulatorControl 
      Visibility="{Binding IsUserControlVisibile, Converter={StaticResource BooleanToVisibilityConverter}}">
</controls:ImageManipulatorControl>

C контролом вообще отдельная беда, при такой записи, что-то прозрачное накладывается на часть экрана(подозреваю что это контрол, хотя bool изначально стоит false)

В базовой VM лежат bool свойства, которые меняются из дочерних VM применяя слово this., но результата никакого

SmiLe
  • 395
  • 1
    Если дочерние VM хотят работать с базовой, почему бы просто не передать ссылку на базовую в конструкторе? Locator нужен скорее для каких-то более продвинутых и сложных сценариев. – VladD Sep 15 '16 at 09:11
  • Конструктор дочерней VM должен получить ссылку на базовую? Или обе должны получать ссылки? – SmiLe Sep 15 '16 at 09:13
  • Ну, я бы делал так, да. Раздал бы в конструкторе дочерних VM им ссылку на базовую VM. – VladD Sep 15 '16 at 09:18
  • А если у меня уже есть экземпляры этих VM в App.xaml, я могу сразу на них сослаться или нужно писать BaseViewModel = new BaseViewModel() – SmiLe Sep 15 '16 at 09:24
  • @VladD Xaml ругается что не может использовать конструкторы, потому что я там указал BaseViewModel – SmiLe Sep 15 '16 at 10:45
  • Я бы не создавал VM-ки в App.xaml, ведь при этом вы не сможете их связать. Лучше делать это в App.xaml.cs. Например, как тут. – VladD Sep 15 '16 at 11:25
  • В App.xaml.cs не могу прописать Datacontext он не хочет использовать это пронстранство имен. Писал даже полностью – SmiLe Sep 15 '16 at 12:07
  • Странно, должно работать. Покажите ваш код и сообщение об ошибке, разберёмся. – VladD Sep 15 '16 at 15:25
  • @VladD Вот http://joxi.ru/8Anz9kDiGBQoAO An object reference is required for the non-static field, method, or property 'FrameworkElement.DataContext' – SmiLe Sep 15 '16 at 15:29
  • Не, вы что-то не совсем то делаете. Посмотрите, как сделано в примере. Вам нужно установить не DataContext объекта App, а главного окна. Которое нужно создать здесь, а не неявно через OnStartup. – VladD Sep 15 '16 at 15:51
  • @VladD Сделал по аналогии, но приложение постоянно крашится потому что не может найти ресурсы из Xaml сначала на VM ругался, я удалил, теперь на конверторы, а они то тут вообще ни при чем Это в конструкторе App `MainViewModel vm = new MainViewModel(); new MainPage() { DataContext = vm
            };`
    
    – SmiLe Sep 15 '16 at 18:57
  • А какие ресурсы? Я ж не могу угадать. Покажите код и сообщение об ошибке. Ну и просто new MainPage() судя по всему неправильно, вы ж должны страницу не только создать, но и показать? – VladD Sep 15 '16 at 19:00
  • @VladD обновил вопрос. Вроде как я понял App.xaml теперь не работвет – SmiLe Sep 15 '16 at 19:16
  • Смотрите, DataContext вам не надо устанавливать в XAML, вы его устанавливаете в коде. Но ещё раз, вы не показываете созданную страницу*! Это не должно быть правильно. Ну а насчёт остальных ошибок, давайте код. – VladD Sep 15 '16 at 19:42
  • @VladD пока не нашел как ее показать, а вот по поводу ошибок,то ругается на все эти ресурсы (app.xaml) http://joxi.ru/J2b4aWEHWxEnr6 Каждый раз как они встречаются в коде в MainPage.xaml в качестве StaticResource Все эти ресурсы нужно перенести в App.cs? – SmiLe Sep 15 '16 at 19:57
  • Нет, с ресурсами всё в порядке, ошибка в другом месте. А вы исправили проблему с тем, что вы не показываете страницу? Думаю, это поправит и другие проблемы. – VladD Sep 15 '16 at 20:02
  • @VladD Вроде показываю страницу. mainPage.Frame.Navigate(typeof(MainPage)); mainPage переменная типа MainPage. Но все равн та же ошибка – SmiLe Sep 15 '16 at 20:15
  • Окей, я даже вроде набросал код, у меня работает. Посмотрите в ответ, и если останутся ошибки, пишите их, разберёмся. – VladD Sep 15 '16 at 21:12

3 Answers3

1

Для решения проблемы создал Singleton класс в котором разместил все нужные свойства. Для обращения к ним использовал

MySingletonClass.InstanceProperty.PropertyToChange=/*value*/

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

"{Binding Instance.IsFilterImgVisible , Source={StaticResource EditModeSwitcher}"
SmiLe
  • 395
0

Ох, это же UWP, там всё странно.

Я не нашёл метод «из коробки», как сделать так, чтобы View не знало о VM. Но это можно симулировать. Не знаю, насколько мой метод хорош, у меня мало опыта с UWP.

Итак, что мы делаем.

Для начала, главную VM и подчинённые VM создаём в классе App:

sealed partial class App : Application
{
    MainVM mainVM;

    public App()
    {
        SetupVM();

        // стандартный код, сгенерированный шаблоном:
        this.InitializeComponent();
        this.Suspending += OnSuspending;
    }

    void SetupVM()
    {
        mainVM = new MainVM();
        var partVM = new PartVM(mainVM);
        var anotherPartVM = new AnotherPartVM(mainVM);
        mainVM.Part = partVM;          
        mainVM.AnotherPart = anotherPartVM;  
    }

Далее, поскольку навигация идёт лишь через Frame.Navigate, в котором невозможно указать экземпляр, а только тип (почему??), я передаю VM как параметр, чтобы окно само себе установило его в качестве VM:

    protected override void OnLaunched(LaunchActivatedEventArgs e)
    {
        // тут много сгенерированного кода

        if (rootFrame.Content == null)
        {
            // здесь добавил mainVM вместо параметра
            rootFrame.Navigate(typeof(MainPage), mainVM);
        }
        // Ensure the current window is active
        Window.Current.Activate();
    }

Теперь в коде страницы:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        // вытаскиваем DataContext из параметров навигации
        DataContext = e.Parameter;
    }

    void OnNextClick(object sender, RoutedEventArgs e)
    {
        // при навигации в другое окно передаём ему DataContext
        Frame.Navigate(typeof(NextPage), DataContext);
        // это новое окно тоже должно будет вытащить его из параметра, как и это
    }
}

Теперь, вложенный UserControl. Ему нужно передать правильный DataContext:

<local:InnerControl DataContext="{Binding Part}"/>

Наконец, при каком-нибудь действии внутри UserControl'а получает управление его VM, которая имеет у себя ссылку на MainVM.

Всё!

VladD
  • 206,799
  • В параметрах навигации рекомендуется передавать примитивные типы ("рекомендуется" в данном случае означает, что только их следует передавать), потому что при засыпании приложения оно будет падать, т.к. не сможет сериализовать что-то сложное. Если надо передать объект, то следует его сериализовать и передать как строку. – Dmitry Sep 16 '16 at 05:41
  • @VladD var partVM = new PartVM(mainVM); Конструктор PartVM не имеет параметров, но принимает 1 аргумент, тоже самое на следующей строке. Если добавить в конструктор PartVM базовую VM тогда говорит, что базовая вью модель это тип, недействителен в текущем датаконтексте http://joxi.ru/RmzYVGOiKWZVrO – SmiLe Sep 16 '16 at 06:51
  • @MakeMakeluv: О господи! А как же тогда передать VM для View снаружи? – VladD Sep 16 '16 at 09:04
  • @SmiLe: Эээ... Ну блин, вы бы учебник почитали для начала. BaseViewModel у вас — это тип, а не переменная. Сформулируйте словами, где у вас главная VM, а где её вложенные, и какие у них типы. В соответствии с этим будет понятно, что делать. – VladD Sep 16 '16 at 09:05
  • @VladD А зачем ее передавать? По теме вопроса - почему надо хранить это свойство именно в ВМ? Можно вынести его как статичное свойство во внешний статичный класс. А еще можно использовать EventAggregator, если опять же юзать Prism. – Dmitry Sep 16 '16 at 09:51
  • @MakeMakeluv: Потому что не дело View устанавливать себе VM. View не имеет права управлять VM (по крайней мере, в MVVM). И ещё проблема в том, что если даже согласиться на то, что View устанавливает себе VM, то тогда View должно и создать и связать все нужные VM, а это уж совсем за рамками его компетенции. – VladD Sep 16 '16 at 10:58
0

Зачем делать свой велосипед, если можно воспользоваться готовым? Prism

Dmitry
  • 1,136