Почему команда связывания кнопок C # WPF не изменит вид после использования простого инжектора? - PullRequest
2 голосов
/ 25 марта 2019

Я использую следующие статьи для начального кода:
Навигация между представлениями в WPF MVVM
Интеграция с простым инжектором WPF

Цель:
Попытка перейти от представления 1 к представлению 2 в форме WPF с помощью команды привязки кнопки и простого инжектора для внедрения зависимостей в представления. Примечание. Эти зависимости являются репозиториями, в которых хранятся данные из внешних источников.

Проблема:
После внедрения зависимостей в мои MainWindow и MainWindowViewModel с помощью Simple Injector мои кнопки больше не меняют мое текущее представление (на другое из моих представлений). При использовании Visual Studio и отладке с использованием точек останова код кажется застрявшим в цикле навсегда в функции CanExecute файла RelayCommand.cs (см. Навигация между представлениями в WPF MVVM ), где что-то вызывает его снова и снова. Я не могу отладить больше в функции CanExecute, потому что передается много кода (из DLL и тому подобного). Когда точки останова не используются, это выглядит так, как будто моя кнопка ничего не делает.

Я не получаю сообщения об ошибках в окне вывода, и никакие исключения не генерируются. Привязка команды работает, потому что я вижу функцию OnGo2Screen, найденную в MainWindowViewModel.cs, вызываемую при отладке. После вызова OnGo2Screen он перемещается по коду, как и ожидалось, пока не застрянет в CanExecute.

Что я пробовал
Я проверил контекст данных моего MainWindow и вижу, что он имеет все правильные функции.

Я сделал отдельный проект для статьи Навигация между представлениями в WPF MVVM , и мне удалось добиться отличного изменения представлений. Но всякий раз, когда я пытаюсь использовать Simple Injector, мои кнопки ломаются.

Я заметил, что когда не используется Simple Injector, код перемещается из функции CanExecute в CanExecuteChanged EventHandler и выполняет удаление и добавление мутаторов, а затем изменяет представление должным образом. Однако при использовании простого инжектора это не выполняется.

Код
Я использую мой App.xaml.cs в качестве программы запуска, в которой мой App.xaml имеет действие «Страница».

SimulationCaseView - это вид 1 (начальный вид по умолчанию).
StreamsView - это представление 2 (просто другое представление).
UserControl3 - это представление 3 (просто другое представление).

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

App.xaml

<Application x:Class="MyApp.Desktop.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:views="clr-namespace:MyApp.Desktop.Views">
    <Application.Resources>
        <DataTemplate DataType="{x:Type views:SimulationCaseViewModel}">
            <views:SimulationCaseView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type views:StreamsViewModel}">
            <views:StreamsView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type views:UserControl3ViewModel}">
            <views:UserControl3 />
        </DataTemplate>
    </Application.Resources>
</Application>

App.xaml.cs

namespace MyApp.Desktop
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        App()
        {
            InitializeComponent();
        }

        [STAThread]
        static void Main()
        {
            var container = Bootstrap();

            // Any additional other configuration, e.g. of your desired MVVM toolkit.

            RunApplication(container);
        }


        private static Container Bootstrap()
        {
            // Create the container as usual.
            var container = new Container();

            // Register your types, for instance:
            container.Register<IPreferencesRepository, PreferencesRepository>(Lifestyle.Singleton);
            container.Register<IStreamRepository, StreamRepository>(Lifestyle.Singleton);

            // Register your windows and view models:
            container.Register<MainWindow>();
            container.Register<MainWindowViewModel>();

            container.Verify();

            return container;
        }

        private static void RunApplication(Container container)
        {
            try
            {
                var app = new App();
                var mainWindow = container.GetInstance<MainWindow>();
                MainWindowViewModel viewModel = container.GetInstance<MainWindowViewModel>();
                mainWindow.DataContext = viewModel;
                app.Run(mainWindow);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

MainWindow.xaml

<Window x:Class="MyApp.Desktop.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyApp.Desktop"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="350" Width="525"
        xmlns:views="clr-namespace:MyApp.Desktop.Views">
    <Grid>
        <ContentControl Content="{Binding CurrentPageViewModel}" />
    </Grid>
</Window>

MainWindowViewModel.cs

namespace MyApp.Desktop.Views
{
    public class MainWindowViewModel : BaseViewModel
    {
        private IPageViewModel _currentPageViewModel;
        private List<IPageViewModel> _pageViewModels;

        public List<IPageViewModel> PageViewModels
        {
            get
            {
                if (_pageViewModels == null)
                    _pageViewModels = new List<IPageViewModel>();

                return _pageViewModels;
            }
        }

        public IPageViewModel CurrentPageViewModel
        {
            get
            {
                return _currentPageViewModel;
            }
            set
            {
                _currentPageViewModel = value;
                OnPropertyChanged("CurrentPageViewModel");
            }
        }

        private void ChangeViewModel(IPageViewModel viewModel)
        {
            if (!PageViewModels.Contains(viewModel))
                PageViewModels.Add(viewModel);

            CurrentPageViewModel = PageViewModels
                .FirstOrDefault(vm => vm == viewModel);
        }

        private void OnGo1Screen(object obj)
        {
            ChangeViewModel(PageViewModels[0]);
        }

        private void OnGo2Screen(object obj)
        {
            ChangeViewModel(PageViewModels[1]);
        }
        private void OnGo3Screen(object obj)
        {
            ChangeViewModel(PageViewModels[2]);
        }

        public MainWindowViewModel(IStreamRepository streamRepository)
        {
            // Add available pages and set page
            PageViewModels.Add(new SimulationCaseViewModel(streamRepository));
            PageViewModels.Add(new StreamsViewModel());
            PageViewModels.Add(new UserControl3ViewModel());

            CurrentPageViewModel = PageViewModels[0];

            Mediator.Subscribe("GoTo1Screen", OnGo1Screen);
            Mediator.Subscribe("GoTo2Screen", OnGo2Screen);
            Mediator.Subscribe("GoTo3Screen", OnGo3Screen);
        }
    }
}

SimulationCaseView.xaml

<UserControl x:Class="MyApp.Desktop.Views.SimulationCaseView"
             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"
             xmlns:local="clr-namespace:MyApp.Desktop"
             mc:Ignorable="d"
             d:DesignHeight="280" d:DesignWidth="280">
    <Grid>
        <Button
            Content="Go to Streams"
            Command="{Binding GoTo2}"
            Width="90" Height="30" Margin="166,220,24,30">
        </Button>
    </Grid>
</UserControl>

SimulationCaseViewModel.cs

namespace MyApp.Desktop.Views
{
    public class SimulationCaseViewModel : BaseViewModel, IPageViewModel
    {
        private ICommand _goTo2;
        private readonly IStreamRepository _repo;
        public SimulationCaseViewModel(IStreamRepository repo)
        {
            _repo = repo;
            Application application = _repo.GetApplicationReference();
            CurrentSimulationCases = new ObservableCollection<SimulationCase>();
            Streams = new ObservableCollection<object>();
            foreach (SimulationCase simulationCase in application.SimulationCases)
            {
                CurrentSimulationCases.Add(simulationCase);
            }
            //FetchStreams = new RelayCommand(OnFetch);
        }

        public ObservableCollection<SimulationCase> CurrentSimulationCases { get; set; }
        public ObservableCollection<object> Streams { get; private set; }

        public ICommand GoTo2
        {
            get
            {
                return _goTo2 ?? (_goTo2 = new RelayCommand(x =>
                {
                    Mediator.Notify("GoTo2Screen", "");
                }));
            }
        }
    }
}

Любая помощь относительно того, почему кнопки не работают, приветствуется. Спасибо.

Ответы [ 2 ]

4 голосов
/ 26 марта 2019

Как решить?

Вызовите этот метод после обновления состояния вашей команды:

CommandManager.InvalidateRequerySuggested();

Почему он не обновляется?

Команды обновляются только тогда, когда этипроисходят общие события:

  • KeyUp
  • MouseUp
  • GotKeyboardFocus
  • LostKeyboardFocus

Для получения подробной информации см. Этот исходный код: CommandDevice.cs

Для других элементов управления имеется больше событий для обновления:

  • IncreaseRepeatButton повторяется при длительном нажатии
  • DataGrid ...
  • SinglePageViewer ...

Вы можете дважды щелкнуть CommandManager.InvalidateRequerySuggested() метод по этой ссылке для просмотра других событий, которые обновляют состояния команды.

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

Другая информация

Вы сказаличто при использовании Visual Studio и отладке с использованием точек останова код кажется застрявшим в цикле навсегда в функции CanExecute файла RelayCommand.cs.

Это не циклp для CanExecute, то есть событий GotKeyboardFocus и LostKeyboardFocus, когда ваше активное окно изменяется между вашим приложением и Visual Studio.

2 голосов
/ 30 марта 2019

Короткий ответ

Проблема в Lifestyle вашей ViewModel, которая должна быть установлена ​​на Singleton, а не на Transient.

по умолчанию.
    private static Container Bootstrap()
    {
        // Create the container as usual.
        var container = new Container();

        // Register your types, for instance:


        // Register your windows and view models:
        //container.Register<MainWindow>(Lifestyle.Singleton); //not needed
        container.Register<MainWindowViewModel>(Lifestyle.Singleton);

        container.Verify();

        return container;
    }

Тогда вы можете запустить приложение простым способом

    private static void RunApplication(Container container)
    {
        try
        {
            var mainWindow = container.GetInstance<MainWindow>();
            var app = new App();
            app.InitializeComponent();
            app.Run(mainWindow);
        }
        catch (Exception ex)
        {
            //Log the exception and exit
            Debug.WriteLine(ex.Message);
        }
    }

Полный код на github .

Длинный ответ - TL; DR

Когда вы вызываете container.Verify в Bootstrap, вы создаете экземпляр MainWindowViewModel для проверки его создания, а другой - для проверки MainWindow класса.

Кстати, вы можете решить свою проблему, просто не проверив контейнер!

Итак, 2 и есть решение

        //container.Register<MainWindow>(); // => Lifestyle.Transient;
        container.Register<MainWindowViewModel>(); // => Lifestyle.Transient;

        //container.Verify();

Теперь обратите внимание, что у вас есть подписка Mediator в MainWindowViewModel c.tor.

    public static void Subscribe(string token, Action<object> callback)
    {
        if (!pl_dict.ContainsKey(token))
        {
            var list = new List<Action<object>>();
            list.Add(callback);
            pl_dict.Add(token, list);
        }
        else
        {
            bool found = false;
            //foreach (var item in pl_dict[token])
            //    if (item.Method.ToString() == callback.Method.ToString())
            //        found = true;
            if (!found)
                pl_dict[token].Add(callback);
        }
    }

Цикл foreach - который я прокомментировал только выше ( и это альтернативный вариант 3 для решения вашей проблемы ) - заставит вас пропустить вызов второй правильный метод ViewModel и оставит вас с первыми неправильными (помните, что проверка Bootstrap создала его дважды). Если вы хотите 4 -ное альтернативное решение, с классическим IComponent интерфейсом Mediator pattern

public interface IComponent
{
     void OnGo1Screen(object obj);
     void OnGo2Screen(object obj);
}
public class MainWindowViewModel : BaseViewModel, IComponent

вы также можете переместить подписку из c.tor

  public MainWindowViewModel()
  {
     // Add available pages and set page
     PageViewModels.Add(new UserControl1ViewModel());
     PageViewModels.Add(new UserControl2ViewModel());

     CurrentPageViewModel = PageViewModels[0];

     //Mediator.Subscribe("GoTo1Screen", OnGo1Screen);
     //Mediator.Subscribe("GoTo2Screen", OnGo2Screen);
  }

в ваш Program:

            var context = mainWindow.DataContext as IComponent;
            Mediator.Subscribe("GoTo1Screen", context.OnGo1Screen);
            Mediator.Subscribe("GoTo2Screen", context.OnGo2Screen);
...