Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I am trying to create a WPF application that presents a login view and after successful login, presents a first, second, and third page (like a wizard). Each "page" including the login view has its respective ViewModel. I have a MainWindow.xaml which holds four UserControls one of which will be visible at any given state.

I am having trouble dealing with the orchestration of visibility. It makes the most sense to me that MainWindowViewModel is the one that is responsible for keeping track of which UserControl is the current visible one but I can't quite seem to get the code to work.

I will only show the relevant files for the MainWindow and the LoginView to keep things simpler.

MainWindow.xaml

<Grid>
    <local:LoginView Visibility="{Not sure what to bind to here}" />
    <local:PageOne Visibility="{Not sure what to bind to here}" />
    <local:PageTwo Visibility="{Not sure what to bind to here}" />
    <local:PageThree Visibility="{Not sure what to bind to here}" />
</Grid>

MainWindow.xaml.cs

public partial class MainWindow : Window
{        
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }     
}

MainWindowViewModel.cs

public class MainWindowViewModel : BaseViewModel { public ICommand WindowClosingCommand { get; }

    public MainWindowViewModel()
    {
        WindowClosingCommand = new WindowClosingCommand(this);
    }
}

LoginView.xaml

<UserControl x:Class="MyProject.View.LoginView"
             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:MyProject.View"
             mc:Ignorable="d" 
             d:DesignHeight="800" d:DesignWidth="1200">

    <Grid>
      <!-- UI Layout stuff here -->
    </Grid>
</UserControl>

LoginView.xaml.cs

public partial class Login : UserControl
{
    public Login()
    {
        InitializeComponent();
        DataContext = new LoginViewModel();
    }
}

LoginViewModel.cs

public class LoginViewModel : BaseViewModel
{
    public ICommand ConnectCommand { get; }
    public ICommand WindowClosingCommand { get; }

    public LoginViewModel()
    {
        ConnectCommand = new ConnectCommand(this);
        WindowClosingCommand = new WindowClosingCommand(this);
    }

    public string UserName { get; set; }
}

So as you can see, I want to avoid putting a ton of logic in the code behind files .xaml.cs because that is best practice and I have a ViewModel for which .xaml file. Now, ordinarily, I would write something like:

public PageType CurrentPage;

public enum PageType
{
    Login, PageOne, PageTwo, PageThree
}

public Visibility LoginVisibility
{
    get { (CurrentPage == PageType.Login) ? Visibility.Visible : Visibility.Collapsed }
}

// Repeat for each of the other three pages

And then depending on if the "Next" or "Back" buttons were clicked on each page, I would set the CurrentPage field properly.

However, if we refer back to my MainWindow.xaml, I can't just do:

<local:LoginView Visibility="{Binding LoginVisibility}" />

Because LoginVisibility does not exist in the LoginViewModel which is what that user control's data context is. And it wouldn't feel right to put that field in there because all ViewModels will then need to know their own visibility state and somehow communicate that up to the MainWindow.

Basically, I am confused and unsure how to go about toggling between pages in my application. Any help or guidance would be greatly appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
144 views
Welcome To Ask or Share your Answers For Others

1 Answer

Instead of binding visibility, you can create data template in the main window resources and bind the appropriate data template to the control template (inside the grid, where you wish to display it) based on the enum changes

A rough idea below.

Inside your mainwindow.xaml

 <Window.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="DTLoginView">
            <local:LoginView />
        </DataTemplate>
        <DataTemplate x:Key="DTPageOne">
            <local:PageOne />
        </DataTemplate>
    </ResourceDictionary>
</Window.Resources>

Now, inside you mainwindow viewmodel, do some logic and based on it, store the values for the page. Your current page property should implement INotifyPropertyChanged which should look something like below. (Note: I have added Haley.Flipper.MVVM nuget package for basic MVVM wiring (Disclaimer: The Haley nuget package is developed by me). You can implement your own INotifyPropertyChanged or use some MVVM libraries)

  private PageType _CurrentPage;
    public PageType CurrentPage
    {
        get { return _CurrentPage; }
        set { _CurrentPage = value; onPropertyChanged(); }
    }

Inside your XAML for MainWindow. (Where you have the grid)

<Grid x:Name="grdPages" DataContext={Binding}>
<ContentControl >
        <ContentControl.Style>
            <Style TargetType="{x:Type ContentControl}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ElementName=grdPages, Path=DataContext.CurrentPage, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="0">
                        <Setter Property="ContentTemplate" Value="{StaticResource DTLoginView}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding ElementName=grdPages, Path=DataContext.CurrentPage, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="1">
                        <Setter Property="ContentTemplate" Value="{StaticResource DTPageOne}"/>
                    </DataTrigger>
               </Style.Triggers>
            </Style>
        </ContentControl.Style>                
    </ContentControl>

If you look at the xaml code above, I have the value as "0" "1" for the datatrigger binding because enums should have 0,1,2,3 and so on. However, you can also directly bind the enum as the values. Do some search and you can easily find answer for that.

The property (enum value) for the current page should be set by some logic (implemented by you). Once that is done, it will automatically trigger the notification to the xaml.

Hope this could help you somehow.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...