How to open one view from another view using MVVM in WPF

二次信任 提交于 2019-12-21 20:35:32

问题


I am new to MVVM. And i am not able to get answer of this problem since a long time. I don't know if question is so difficult or i have not explained properly. I have MainWindow.Xaml which contains a textblock and a button to receive the data from textblock and now when i press a button it should open second View called tableView.xaml (I have already created User Control for it/Xaml) .

I have two question now (Please note that i am following MVVM while answering)?

(1) How to open this View "tableView.xaml" from button click by closing the current opened MainWindow.xaml form (this button click is binded using MVVM) ?

My code for button click where this new form must open (by closing the current MainWindow.xaml) is here (so i guess the code for opening tableView.xaml must be somewhere here only):

 public void SaveExecuted() //some where here i have to open "tableView.Xaml"
 {
   System.Windows.MessageBox.Show(string.Format("Saved: {0} {1} ", DB_Col.DbName, DB_Col.NumTables));
 }
 public ICommand SavePersonCommand
        {
            get
            {
                if (savePersonCommand == null)
                    savePersonCommand = new DelegateCommand(new Action(SaveExecuted), new Func<bool>(SaveCanExecute));
                return savePersonCommand;
            }
        }

But how to do that ?

(2) The "tableView.Xaml" has to contain the GUI which will be written in c# dynamically according to the input i received in previous MainWindow.xaml on "Save Button" click and they must be added to tableView.Xaml. So how and where to write that C# code so that the GUI generated by this c# code will be rendered on tableView.Xaml ?

Summary: Could some one please let me know how to open the tableView.Xaml from Button click and how to append/render the GUI on tableView.Xaml (I know how to write the c# code to Generate GUI but where to write that code such that it will render GUI in tableView.Xaml respecting the MVVM rules).

EDIT: I feel like still dear helpers are not able to understand what i am trying to do, So please see it for more detail :

What i have to do is: I have MainWindow.xaml which contains textbox "Enter number of tables" which is receiving input from user (as Number of tables he want to create).And user after entering this input he will click save button. Now saving the button must close the MainWindow.xaml and will launch new usercontrol which is "tableView.xaml". Suppose he enter 4 and save. Now in newly launched tableView.xaml i have to show text box which will receive "Name Of table", "Nmbr of columns", if he enter 3 for Number of columns, then there should be 3 more text box to receive name of each column and data type and Primary key and save button at last.

And this GUI now has to repeat 4 times because in Mainwondow.xaml the user enter 4 in "Number of tables" option dynamically at starting. So we have to repeat this GUI 4 times for each table entry in "tableView.xaml" that's why i am writing code in c# for GUI generation and after GUI generation i will render that GUI obtained from C# code to tableView.xaml. Now you understood ? If you know any other way of doing this using MVVM then you are most welcome to give me a little sample. By generating GUI dynamically using C# code mean i have to do something like :

Now when i got Input from user in MainWindow.xaml as 4 the i repeat below GUI 4 times (in for loop inside a big container) and render it to tableView.Xaml (I am doing it in c# because i have to repeat it dynamically according to the user's choice in MainWindow.xaml form).

StackPanel stp = new StackPanel();
TextBlock txt1 = new TextBlock();
stp.Children.Add(txt1);  

This stp must go to tableView.xaml and must render 4 stackpanel each containing textblock.


回答1:


1) You're still not being very clear in your exact requirements which makes it very difficult to answer this question. You say you want to "close the current MainWindow.xaml form" yet you seem to indicate your tableView is a UserControl. Are you trying to shut down MainWindow and replace it entirely with a completely different window? If so then what's the point? If the original MainWindow is closing why don't you just change the contents of that Window?

2) This is a big topic and again you're going to have to provide more information as to exactly what you're trying to do, but in general you use DataTemplating. You start by declaring view models for the GUI elements you wish to create, as well a parent VM that contains a list of them:

public abstract class GuiItem { } // base class

public class TextBlockVM : GuiItem { }
public class CheckBoxVM : GuiItem { }
public class TextBoxVM : GuiItem { }

public class CustomViewModel : ViewModelBase
{
    private GuiItem[] _GuiItems = new GuiItem[]
    {
        new TextBlockVM{},
        new CheckBoxVM{},
        new TextBoxVM{}
    };
    public GuiItem[] GuiItems { get { return this._GuiItems; } }
}

Then you create an ItemsControl, binding ItemsSource to the array, specifying what type of panel you want to draw them on and containing DataTemplates that specify how to template each VM in the GUI:

<ItemsControl ItemsSource="{Binding GuiItems}" Margin="10">

    <ItemsControl.Resources>

        <DataTemplate DataType="{x:Type local:TextBlockVM}">
            <TextBlock Text="This is a TextBlock" />
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:CheckBoxVM}">
            <CheckBox>This is a CheckBox</CheckBox>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:TextBoxVM}">
            <TextBox Width="100" HorizontalAlignment="Left"/>
        </DataTemplate>

    </ItemsControl.Resources>

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

</ItemsControl>

Result:

Obviously this is a very simple example just to show the general idea, in a real-world app you'd also add fields to the view models to specify the text and command handlers etc




回答2:


Ok, first of all, you should define navigation system of your app. Is it main windows with child windows? is it frame with pages? Is it main window with tabs? Then create class that will cover this logic.

E.g. to show child window from viewmodel and pass some inputs to it, you can do following:

public class MainWindowViewModel
{
    private IDialogService _dialogService;
    public MainWindowViewModel(IDialogService dialogService)
    {
        _dialogService = dialogService;
        SaveCommand = new DelegateCommand(Save);
    }

    //implement PropertyChanged if needed
    public string SomeInput { get; set; }

    public DelegateCommand SaveCommand { get; private set; }

    private void Save()
    {
        var tableViewModel = new TableViewModel();
        tableViewModel.SomeInput = this.SomeInput;

        _dialogService.ShowModal(new DialogOptions
        {
            Title = "Table view",
            Content = tableViewModel 
        });
    }
}

if you needed to navigate to another page rather than open new window, you can easily create NavigationService instead of DialogService. Idea is the same.

Why am I not creating child window directly in viewmodel? because window is view and I want to keep separation of concerns.

Why am I using IDialogService instead of concrete class? One of the MVVM principles is testability. at runtime, concrete class that opens real windows will be used, but in test I can easily create mock, that won't open windows.

In general, if you want to close window from viewmodel, you create and call some event (e.g CloseRequested) in viewmodel and window should listen to the event:

public class MainWindowViewModel
{
    public event EventHandler CloseRequested;

    private void Close()
    {
        var closeRequested = CloseRequested;
        if (closeRequested != null) closeRequested (this, EventArgs.Empty);
    }
}

//mainwinow.xaml.cs
     public MainWindow()
     {
         InitializeComponent();

         var viewModel = new MainWindowViewModel();
         viewModel.CloseRequested += (sender, args) => this.Close();
         DataContext = viewModel;
     }



回答3:


I believe that pure MVVM is a little too difficult and overengineered for you yet. You are probably not able to take full advantage of it.

Let's try to simplify it by combining MVVM and classical approach:

Create MainWindow.xaml, MainWindowViewModel, TableView.xaml and TableViewModel:

public class MainWindowViewModel 
{    
   //implement INotifyPropertyChanged if needed
   public int NumberOfRows { get; set; }
   public int NumberOfColumns { get; set; }

    public void Save()
    {
        //do something if needed
    }
}

MainWindow.xaml:

<StackPanel>
    <TextBlock Text="Rows" />
    <TextBox Text="{Binding NumberOfRows}" />

    <TextBlock Text="Columns" />
    <TextBox Text="{Binding NumberOfColumns}" />

    <Button Content="Save" Click="Save_Click" />
</StackPanel>

MainWindow.xaml.cs:

public partial class MainWindow : Window
{

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }

    MainWindowViewModel ViewModel
    {
        get { return (MainWindowViewModel)DataContext; }
    }

    private void Save_Click(object sender, RoutedEventArgs e)
    {
        ViewModel.Save();

        var tableViewWindow = new TableView(ViewModel.NumberOfRows, ViewModel.NumberOfColumns);
        this.Close();
        tableViewWindow.Show();
    }
}

TableView.xaml.cs

public partial class TableView: Window
{

    public TableView(int rows, int colums)
    {
        InitializeComponent();

        DataContext = new TableViewModel();
        //do whatever needed with rows and columns parameters. 
        //You will probably need then in TableViewModel
    }

    TableViewModel ViewModel
    {
        get { return (TableViewModel )DataContext; }
    }
}


来源:https://stackoverflow.com/questions/32550403/how-to-open-one-view-from-another-view-using-mvvm-in-wpf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!