亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

MVVM 視圖之間切換

MVVM 視圖之間切換

PHP
BIG陽 2024-01-20 15:55:21
我是 WPF 新手,請耐心等待。我有一個 WinForms 應用程序,我正在嘗試在 WPF 中重做。在我當前的 WinForms 應用程序中,我將所有控件粘貼到一個窗體中,并根據點擊的按鈕隱藏/顯示它們,以及使用第二個窗體。我的目標:創建不同的視圖,以便根據按下的按鈕在不同的視圖之間平滑切換,而不是隱藏控件或制作單獨的表單,然后隱藏它們。我當前有一個 MainWindow 視圖(我的初始啟動窗口),通過一個按鈕,我可以切換到 CreateAccount 視圖。我遇到的問題是,如何使 CreateAccount 中的按鈕“返回”主窗口?我的最終目標是能夠根據按鈕點擊在 4 個視圖之間切換。這是我的 MainWindow.xaml<Window x:Class="MusicPlayer.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:MusicPlayer"        xmlns:Views="clr-namespace:MusicPlayer.Views"        xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"        Title="MainWindow" Height="450" Width="800">    <Window.Resources>        <DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">            <Views:CreateAccountView DataContext="{Binding}"/>        </DataTemplate>    </Window.Resources>    <Grid>        <Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>        <PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>        <ContentPresenter Content="{Binding}"/>    </Grid></Window>我的 MainWindow.xaml.csusing System;using System.Windows;using MusicPlayer.ViewModels;namespace MusicPlayer {    public partial class MainWindow : Window {        public MainWindow() {            InitializeComponent();        }
查看完整描述

1 回答

?
九州編程

TA貢獻1785條經驗 獲得超4個贊

在我看來,你目前的嘗試是在正確的軌道上。您發布的代碼的主要問題是CreateAccountView.Button_Click()處理程序無權訪問DataContext它應該設置的屬性:

private void Button_Click(object sender, RoutedEventArgs e) {
    DataContext = new MainWindowViewModel();
}

DataContext屬性屬于CreateAccountView用戶控件。但是,這不是所顯示內容的控制上下文。因此更改該屬性的值DataContext不會產生任何有用的效果。(事實上,用戶控件DataContext根本不應該設置自己的屬性,因為這樣做會丟棄使用該用戶控件的客戶端代碼設置的任何上下文。)

沒有足夠的上下文來確切地知道執行此操作的最佳方法是什么。我認為在 Stack Overflow 上不可能提供足夠的上下文。整體架構將取決于程序的太多小細節。但是,我認為解決這個問題的一種很好的方法是:

  • 創建一個“主”視圖模型來管理應用程序的整體行為

  • 創建與 UI 的不同狀態相關的單獨視圖模型

  • 讓主視圖模型配置各個視圖模型,根據用戶輸入(例如單擊按鈕)適當切換當前視圖模型

將其翻譯成代碼,看起來像這樣......

首先,視圖模型:

class MainViewModel : NotifyPropertyChangedBase

{

    private object _currentViewModel;

    public object CurrentViewModel

    {

        get => _currentViewModel;

        set => _UpdateField(ref _currentViewModel, value);

    }


    private readonly HomeViewModel _homeViewModel;

    private readonly Sub1ViewModel _sub1ViewModel;

    private readonly Sub2ViewModel _sub2ViewModel;


    public MainViewModel()

    {

        _sub1ViewModel = new Sub1ViewModel

        {

            BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)

        };


        _sub2ViewModel = new Sub2ViewModel

        {

            BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)

        };


        _homeViewModel = new HomeViewModel

        {

            ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),

            ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)

        };


        CurrentViewModel = _homeViewModel;

    }

}


class HomeViewModel : NotifyPropertyChangedBase

{

    private ICommand _showSub1Command;

    public ICommand ShowSub1Command

    {

        get => _showSub1Command;

        set => _UpdateField(ref _showSub1Command, value);

    }


    private ICommand _showSub2Command;

    public ICommand ShowSub2Command

    {

        get => _showSub2Command;

        set => _UpdateField(ref _showSub2Command, value);

    }

}


class Sub1ViewModel : NotifyPropertyChangedBase

{

    private ICommand _backCommand;

    public ICommand BackCommand

    {

        get => _backCommand;

        set => _UpdateField(ref _backCommand, value);

    }

}


class Sub2ViewModel : NotifyPropertyChangedBase

{

    private ICommand _backCommand;

    public ICommand BackCommand

    {

        get => _backCommand;

        set => _UpdateField(ref _backCommand, value);

    }

}

當然,這些視圖模型只包含處理 UI 切換所需的實現細節。在您的程序中,每個視圖狀態還包含您需要的特定于每個視圖狀態的內容。


在我的小示例中,“主頁”視圖包含幾個按鈕,用于選擇可用的各個子視圖:


<UserControl x:Class="WpfApp1.HomeView"

             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" 

             mc:Ignorable="d" 

             d:DesignHeight="450" d:DesignWidth="800">

  <StackPanel Orientation="Horizontal">

    <TextBlock Text="Home: "/>

    <Button Content="Sub1" Command="{Binding ShowSub1Command}"/>

    <Button Content="Sub2" Command="{Binding ShowSub2Command}"/>

  </StackPanel>

</UserControl>

子視圖僅包含返回主視圖所需的按鈕:


<UserControl x:Class="WpfApp1.Sub1View"

             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" 

             mc:Ignorable="d" 

             d:DesignHeight="450" d:DesignWidth="800">

  <StackPanel Orientation="Horizontal">

    <TextBlock Text="Sub1 View: "/>

    <Button Content="Back" Command="{Binding BackCommand}"/>

  </StackPanel>

</UserControl>


<UserControl x:Class="WpfApp1.Sub2View"

             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" 

             mc:Ignorable="d" 

             d:DesignHeight="450" d:DesignWidth="800">

  <StackPanel Orientation="Horizontal">

    <TextBlock Text="Sub2 View: "/>

    <Button Content="Back" Command="{Binding BackCommand}"/>

  </StackPanel>

</UserControl>

最后,主窗口設置主視圖模型,并聲明用于每個特定子視圖的模板:


<Window x:Class="WpfApp1.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:l="clr-namespace:WpfApp1"

        mc:Ignorable="d"

        Title="MainWindow" Height="450" Width="800">

  <Window.DataContext>

    <l:MainViewModel/>

  </Window.DataContext>

  <Window.Resources>

    <DataTemplate DataType="{x:Type l:HomeViewModel}">

      <l:HomeView/>

    </DataTemplate>

    <DataTemplate DataType="{x:Type l:Sub1ViewModel}">

      <l:Sub1View/>

    </DataTemplate>

    <DataTemplate DataType="{x:Type l:Sub2ViewModel}">

      <l:Sub2View/>

    </DataTemplate>

  </Window.Resources>

  <StackPanel>

    <ContentControl Content="{Binding CurrentViewModel}"/>

  </StackPanel>

</Window>

重要的是,您將看到所有視圖對象都不包含任何隱藏代碼。當您以這種方式處理問題時,沒有必要這樣做,至少不是為了控制代碼中的基本行為。(您可能仍然會遇到視圖對象的代碼隱藏,但這通常只是為了實現該視圖對象特有的特定用戶界面行為,而不是為了處理視圖模型狀態。)


使用這種方法,您可以讓 WPF 完成盡可能多的繁重工作。它還將所有視圖模型對象相互解耦。有一個清晰的層次結構:只有頂級“主”視圖模型才知道其他視圖模型。這允許子視圖模型(“home”、“sub1”和“sub2”)根據需要在其他場景中重用,而無需在其中進行任何修改或特殊情況處理。



這是我上面使用的輔助類:


class NotifyPropertyChangedBase : INotifyPropertyChanged

{

    public event PropertyChangedEventHandler PropertyChanged;


    protected void _UpdateField<T>(ref T field, T newValue,

        Action<T> onChangedCallback = null,

        [CallerMemberName] string propertyName = null)

    {

        if (EqualityComparer<T>.Default.Equals(field, newValue))

        {

            return;

        }


        T oldValue = field;


        field = newValue;

        onChangedCallback?.Invoke(oldValue);

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    }

}

class DelegateCommand : ICommand

{

    private readonly Action _execute;


    public DelegateCommand(Action execute)

    {

        _execute = execute;

    }


#pragma warning disable 67

    public event EventHandler CanExecuteChanged;

#pragma warning restore


    public bool CanExecute(object parameter) => true;


    public void Execute(object parameter) => _execute();

}


查看完整回答
反對 回復 2024-01-20
  • 1 回答
  • 0 關注
  • 179 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號