0

In window Mainwindow.xaml has a frame

<Frame x:Name="Main" 
       Source="/View/Home.xaml"
       NavigationUIVisibility="Hidden"
       DataContext="{StaticResource MainWindowVM}">
</Frame>

In page Home.xaml has a listview

<ListView x:Name="ListViewStore" 
          ItemsSource="{Binding ListStore}"
          SelectionMode="Single"
          ScrollViewer.HorizontalScrollBarVisibility="Disabled"
          ScrollViewer.VerticalScrollBarVisibility="Hidden">
    <ListView.ItemTemplate>
        <DataTemplate>
          <!--2 textblocks that display StoreName and StoreAddress-->
        </DataTemplate>
    </ListView.ItemTemplate>
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel/>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding StoreDetailCommand}"
                                   CommandParameter="{Binding ElementName=Home}">         </i:InvokeCommandAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListView>

In HomeViewModel

public ICommand StoreDetailCommand { get; set; }
public HomeViewModel()
{
    StoreDetailCommand = new RelayCommand<Home>((p) => { return p.ListViewStore.SelectedItem == null ? false : true; }, (p) => displayStoreDetail(p));
}
void displayStoreDetail(Home parameter)
{
    
}

When a user clicks on a store item in ListViewStore, the page StoreDetail.xaml with information about the store item appears on the Main frame. How can I navigate and pass data to StoreDetail.xaml? I am delighted that there is any possibility of returning to the previous Home.xaml while maintaining data order. Thank you for spending time with me.

I had google for it but seem no ideas

Edit: I create static Instance in MainWindow.xaml.cs then use MainWindow.Instance.Main.NavigationService.Navigate(new Page()) and it navigate to new page successfully. I wonder each time I access an element in another view: should I create a new static instance? In this case, I'd like to learn good coding habits. Thanks

Kayyy
  • 1
  • 2
  • 1
    I recommend usercontrols rather than pages and viewmodel first navigation. https://social.technet.microsoft.com/wiki/contents/articles/52485.wpf-tips-and-tricks-using-contentcontrol-instead-of-frame-and-page-for-navigation.aspx – Andy Jan 18 '23 at 10:15
  • [C# WPF Navigation Between Pages (Views)](https://stackoverflow.com/a/61323201/3141792), [How do I toggle between pages in a WPF application?](https://stackoverflow.com/a/58849975/3141792) – BionicCode Jan 19 '23 at 09:37

1 Answers1

0

Bit long for a comment but some further explanation seems advisable.

The article I linked to does simplistic instantiation of viewmodels.

You need something more sophisticated in a commercial app, but let's gloss over such huge subjects as dependency injection.

Something must instantiate your viewmodel and that is probably best handled by a factory which can be as simple as a lambda.

Most of this is air code so apologies for mis spelling. The intention is to give you a flavour of what you need to do. Which is fairly extensive.

In your app you would add a dictionary:

Dictionary<Type, Object> AllMyViewModels;

This will store the state of previously used viewmodels which will in turn allow you to store state of whatever the user was last doing in FooView because you bind everything you care about to FooViewModel.

Let's imagine this is mainwindow and a single window app. That has mainwindowviewmodel and maybe that contains that ViewModels dictionary if this is a small app.

Here's a prototype window. The loginuc and useruc are usercontrols.

    Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:LoginViewModel}">
            <local:LoginUC/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:UserViewModel}">
            <local:UserUC/>
        </DataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ItemsControl ItemsSource="{Binding NavigationViewModelTypes}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding Name}"
                        Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                        CommandParameter="{Binding VMType}"
                    />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <ContentPresenter Grid.Column="1"
                          Content="{Binding CurrentViewModel}"
                          />
    </Grid>
</Window>

Click one of those buttons in the itemscontrol and navigation will happen by switching out CurrentViewModel. That will be templated out into a user control in the view.

This is some code I happen to have so it could easily be improved by using something like the community mvvm toolkit. This is just to give you ( or anyone ) the idea though.

MainWindowViewModel

public class MainWindowViewModel : INotifyPropertyChanged
{
    public string MainWinVMString { get; set; } = "Hello from MainWindoViewModel";

    public ObservableCollection<TypeAndDisplay> NavigationViewModelTypes { get; set; } = new ObservableCollection<TypeAndDisplay>
        (
        new List<TypeAndDisplay>
        {
           new TypeAndDisplay{ Name="Log In", VMType= typeof(LoginViewModel) },
           new TypeAndDisplay{ Name="User", VMType= typeof(UserViewModel) }
        }
        );

    private object currentViewModel;

    public object CurrentViewModel
    {
        get { return currentViewModel; }
        set { currentViewModel = value; RaisePropertyChanged(); }
    }
    private RelayCommand<Type> navigateCommand;
    public RelayCommand<Type> NavigateCommand
    {
        get
        {
            return navigateCommand
              ?? (navigateCommand = new RelayCommand<Type>(
                vmType =>
                {
                    CurrentViewModel = null;
                    CurrentViewModel = Activator.CreateInstance(vmType);
                }));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

As it is, type is used to instantiate a viewmodel without any parameters. That happens every time in the code above. We can extend that by using our dictionary though.

Add on

    private Dictionary<Type, Object> viewModels = new Dictionary<Type, Object>();

And change the command

              ?? (navigateCommand = new RelayCommand<Type>(
                vmType =>
                {
                    CurrentViewModel = null;
                    if (viewModels.ContainsKey(vmType))
                    {
                        CurrentViewModel = viewModels[vmType];
                        return;
                    }
                    CurrentViewModel = Activator.CreateInstance(vmType);
                    viewModels.Add(vmType, CurrentViewModel);
                }));

Now, we add any viewmodel to the dictionary if it isn't there or return what's in that dictionary if we have an appropriate viewmodel.

You could add a factory into this for each viewmodel which passes in parameters.

Other areas you could improve this include using dependency injection. Some viewmodels you probably don't want to retain state. You can differentiate in the dependency injection container when you register your viewmodels and specify whether they should be singleton or transient ( and a new instance ). The DI container would replace the dictionary.

Usually, parameters you want for a viewmodel are services like repository which in turn have dependencies you'd want to use DI for.

Andy
  • 11,864
  • 2
  • 17
  • 20
  • Thanks for your detailed answer, @Andy. That's quite new to me, and I will take time to understand it deeper. Thanks again – Kayyy Jan 18 '23 at 12:18
  • Different way of thinking but I find viewmodel first solves a number of problems you come across in real world apps. And then there is dependency injection which is a must in commercial apps and unit testing.... List goes on and on. I think Pages were invented for wpf in a browser - xbap. – Andy Jan 18 '23 at 12:58