如何使用MVVM在WPF主窗口中显示用户控件

时间:2021-03-02 19:29:23

I have a WPF application and just embarked in learning MVVM pattern.

我有一个WPF应用程序,刚刚开始学习MVVM模式。

My goal is that in my application the main window has a button. When this button is clicked, another window (or user control) will appear on top of the main window.

我的目标是在我的应用程序中,主窗口有一个按钮。当单击此按钮时,另一个窗口(或用户控件)将出现在主窗口的顶部。

This is the code of MainWindow.xaml

这是MainWindow.xaml的代码

<Window x:Class="SmartPole1080.View.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
    xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
    xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
    xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
    xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
    xmlns:view1="clr-namespace:SmartPole1080.View"
    xmlns:behaviors="clr-namespace:SoltaLabs.Avalon.Core.Behaviors;assembly=SoltaLabs.Avalon.Core"
    Title="Smart Pole" 
    WindowStartupLocation="CenterScreen"
    Name="mainWindow"
    behaviors:IdleBehavior.IsAutoReset="True" WindowState="Maximized" WindowStyle="None">
<Canvas Background="DarkGray">
    <!--Main Grid-->
    <Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
          HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <StackPanel Background="Black">                
            <Grid Background="#253341">
                <Grid.RowDefinitions>
                    <RowDefinition Height="5"/>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="5"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="264"/>
                </Grid.ColumnDefinitions>

                <Grid Grid.Row="1" Grid.Column="1">
                    <Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
                            Command="{Binding ShowEmergencyPanel}">
                        <Image Source="{StaticResource EmergencyImg}" />
                    </Button>
                </Grid>

                <!--Emergency Window Dialog-->
                <Grid Name="EmergencyPanel">
                    <view1:EmergencyInfo x:Name="EmergencyInfoPanel"/>
                </Grid>
            </Grid>
        </StackPanel>
    </Grid>
    <!--Main Grid-->
</Canvas>

This is the other window (user control - EmergencyInfo.xaml)

这是另一个窗口(用户控制- emergency - info .xaml)

<UserControl x:Class="SmartPole1080.View.EmergencyInfo"
         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:SmartPole1080.View"
         mc:Ignorable="d" 
         d:DesignHeight="1920" d:DesignWidth="1050"
         x:Name="EmergencyInfoPanel">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="50"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Border Grid.Row="0" BorderBrush="White" Background="White">
        <Button Background="White" BorderThickness="0" FontWeight="Bold" Foreground="Red" 
                HorizontalAlignment="Right" FontSize="25" Margin="0,0,15,0"
                Command="{Binding HideEmergencyPanel}">
            close X
        </Button>
    </Border>
    <Image Grid.Row="1" Source="{StaticResource EdenParkInfoImg}" HorizontalAlignment="Left" />
</Grid>

I want to implement this behavior using an MVVM pattern. I have set the binding ShowEmergencyPanel in button EmergencyButton to show EmergencyInfo when this button is click.

我想使用MVVM模式实现此行为。我已经将绑定的showemergency面板设置为在单击按钮时显示EmergencyInfo。

Any help is greatly appreciated.

非常感谢您的帮助。

3 个解决方案

#1


5  

Why dont you make navigation, something like this. Make section for conetent that will be injected, and whatever type of object you are expecting put it in Windows.Resources in DataTemplate.

你为什么不做导航,像这样。为将要被注入的conetent创建部分,以及您期望的任何类型的对象将其放入Windows中。在DataTemplate资源。

In main windo xaml

在主windo xaml

 <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Window.Resources>
        <DataTemplate DataType="{x:Type home:HomeViewModel}">
            <home:HomeView />
        </DataTemplate>

        <DataTemplate DataType="{x:Type other:OtherViewModel}">
            <other:OtherView />
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid x:Name="Navigation">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="HomeView"
                        Content="Home"
                        Margin="5"
                        Command="{Binding NavigationCommand}"
                        CommandParameter="home" />
                <Button x:Name="Menu"
                        Content="OtherView"
                        Margin="5"
                        Command="{Binding NavigationCommand}"
                        CommandParameter="Other" />
            </StackPanel>
        </Grid>
        <Grid x:Name="MainContent"
              Grid.Row="1">
            <ContentControl Content="{Binding CurrentViewModel}" />
        </Grid>

    </Grid>

MainWindowViewModel can look something like this.

MainWindowViewModel可以像这样。

public class MainWindowViewModel : INotifyPropertyChanged
    {
        private OtherViewModel otherVM;
        private HomeViewModel homeVM;

        public DelegateCommand<string> NavigationCommand { get; private set; }

        public MainWindowViewModel()
        {
            otherVM = new OtherViewModel();
            homeVM = new HomeViewModel();

            // Setting default: homeViewModela.
            CurrentViewModel = homeVM;

            NavigationCommand = new DelegateCommand<string>(OnNavigate);
        }

        private void OnNavigate(string navPath)
        {
            switch (navPath)
            {
                case "other":
                    CurrentViewModel = otherVM;
                    break;
                case "home":
                    CurrentViewModel = homeVM;
                    break;
            }
        }

        private object _currentViewModel;
        public object CurrentViewModel
        {
            get { return _currentViewModel; }
            set
            {
                if (_currentViewModel != value)
                {
                    _currentViewModel = value;
                    OnPropertyChanged();
                }
            }
        }


        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
          PropertyChanged(this, new propertyChangedEventArgs(propertyName));
        }
        #endregion
    }

Where DelegateCommand you can make yours,check how to make RelayCommand(and generic one) or use PRISM that have it's own DelegateCommand. But if you want to use PRISM, it allready has navigation to regions, that can solve many problems. Check videos from Brian Lagunas.

在你可以做你的委托的时候,检查一下如何制作RelayCommand(和通用的)或者使用有自己授权的PRISM。但是如果你想要使用PRISM,那么allready就有区域导航,这可以解决很多问题。查看Brian Lagunas的视频。

EDIT: This is to show/hide grid. In your mainWindow where you set that EmergencyInfo u can show/hide that grid this way.

编辑:这是显示/隐藏网格。在你的主窗口中,你设置的紧急信息u可以这样显示/隐藏网格。

in your MainViewViewModel make a bool property IsVisible

在mainviewmodel中,使bool属性可见

private bool _isVisible;
        public bool IsVisible
        {
            get { return _isVisible; }
            set
            {
                _isVisible = value;
                OnPropertyChanged();
            }
        }

in your MainView.Resources set key to BooleanToVisibilityConverter something like:

MainView。参考资料设置了BooleanToVisibilityConverter的键,如下所示:

 <BooleanToVisibilityConverter x:Key="Show"/>

and your grid that you want to show/hide set visibility:

以及您想要显示/隐藏设置可见性的网格:

<Grid x:Name="SomeGridName"
              Grid.Row="1"
              Grid.Colum="1"
              Visibility="{Binding IsVisible,Converter={StaticResource Show}}">

And finally set that IsVisible property to some ToggleButton, just to switch between true/false

最后将IsVisible属性设置为ToggleButton,在true/false之间切换

 <ToggleButton IsChecked="{Binding IsVisible}"
                              Content="ShowGrid" />

This way, you show/hide that grid part based on IsVisible, and you control that visibility onClick. Hope that helps.

通过这种方式,您可以显示/隐藏基于IsVisible的网格部分,并控制该可见性onClick。希望有帮助。

#2


2  

Inside your Window xaml:

在你窗口xaml:

 <ContentPresenter Content="{Binding Control}"></ContentPresenter>

In this way, your ViewModel must contain a Control property.

这样,您的ViewModel必须包含一个控件属性。

Add your ViewModel to DataContext of Window. (For example in window constructor, this.Datacontext = new ViewModel();)

将ViewModel添加到窗口的DataContext中。(例如在窗口构造函数中,这个。Datacontext = new ViewModel();

Or another way with interfaces:

或者是另一种接口方式:

 public interface IWindowView
 {
        IUserControlKeeper ViewModel { get; set; }
 }

 public interface IUserControlKeeper 
 {
        UserControl Control { get; set; }
 }


 public partial class YourWindow : IViewWindow
 {
        public YourWindow()
        {
            InitializeComponent();
        }

        public IUserControlKeeper ViewModel
        {
            get
            {
                return (IUserControlKeeper)DataContext;
            }

            set
            {
                DataContext = value;
            }
        }
  }

(In this way, initialize your window where you want to use. Service?)

(用这种方法,初始化您想要使用的窗口。服务吗?)

private IViewWindow _window;
private IViewWindow Window  //or public...
{
  get{
       if(_window==null)
       {
           _window = new YourWindow();
           _window.ViewModel = new YourViewModel();
       }
       return _window;
     }
}

Open your window with one of your UserControls:

使用您的用户控件之一打开您的窗口:

  void ShowWindowWithControl(object ControlView, INotifyPropertyChanged ControlViewModel, bool ShowAsDialog)
  {
        if(ControlView is UserControl)
        {       //with interface: Window.ViewModel.Control = ...
               (Window.DataContext as YourViewModel).Control = (UserControl)ControlView;

               if (ControlViewModel != null)
                   (Window.DataContext as YourViewModel).Control.DataContext = ControlViewModel;

               if (ShowAsDialog) //with interface use cast to call the show...
                   Window.ShowDialog();
               else
                   Window.Show();

         }
  }

#3


0  

What you can do is, place the Emergency usercontrol inside MainGrid and control its visibility through Model property.

您可以做的是,将紧急用户控件放置在MainGrid中,并通过模型属性控制其可见性。

    <Canvas Background="DarkGray">
    <!--Main Grid-->
    <Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
          HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <StackPanel Background="Black">                
            <Grid Background="#253341">
                <Grid.RowDefinitions>
                    <RowDefinition Height="5"/>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="5"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="264"/>
                </Grid.ColumnDefinitions>

                <Grid Grid.Row="1" Grid.Column="1">
                    <Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
                            Command="{Binding ShowEmergencyPanel}">
                        <Image Source="{StaticResource EmergencyImg}" />
                    </Button>
                </Grid>


            </Grid>
        </StackPanel>

        <Grid Name="EmergencyPanel">
            <view1:EmergencyInfo x:Name="EmergencyInfoPanel" Visibility={Binding IsEmergencyPanelVisible,Converter={StaticResource BoolToVisibilityConverter}} DataContext={Binding} />
        </Grid>
    </Grid>
    <!--Main Grid-->
</Canvas>

By setting proper background to Grid that holding usercontrol, you can achieve modal window like effect. And you need to have IsEmergencyPanelVisible(default value is false) is your Model, and this property should be changed to true on your button click command.

通过在网格中设置合适的背景来控制用户控件,可以实现模式窗口效果。您需要让isemergency cypanelvisible(默认值为false)是您的模型,这个属性应该在您的按钮单击命令中将其更改为true。

Note : I guess you are familiar with converters, so i included Converters in my solution.

注意:我想你对转换器很熟悉,所以我在解决方案中加入了转换器。

#1


5  

Why dont you make navigation, something like this. Make section for conetent that will be injected, and whatever type of object you are expecting put it in Windows.Resources in DataTemplate.

你为什么不做导航,像这样。为将要被注入的conetent创建部分,以及您期望的任何类型的对象将其放入Windows中。在DataTemplate资源。

In main windo xaml

在主windo xaml

 <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Window.Resources>
        <DataTemplate DataType="{x:Type home:HomeViewModel}">
            <home:HomeView />
        </DataTemplate>

        <DataTemplate DataType="{x:Type other:OtherViewModel}">
            <other:OtherView />
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid x:Name="Navigation">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="HomeView"
                        Content="Home"
                        Margin="5"
                        Command="{Binding NavigationCommand}"
                        CommandParameter="home" />
                <Button x:Name="Menu"
                        Content="OtherView"
                        Margin="5"
                        Command="{Binding NavigationCommand}"
                        CommandParameter="Other" />
            </StackPanel>
        </Grid>
        <Grid x:Name="MainContent"
              Grid.Row="1">
            <ContentControl Content="{Binding CurrentViewModel}" />
        </Grid>

    </Grid>

MainWindowViewModel can look something like this.

MainWindowViewModel可以像这样。

public class MainWindowViewModel : INotifyPropertyChanged
    {
        private OtherViewModel otherVM;
        private HomeViewModel homeVM;

        public DelegateCommand<string> NavigationCommand { get; private set; }

        public MainWindowViewModel()
        {
            otherVM = new OtherViewModel();
            homeVM = new HomeViewModel();

            // Setting default: homeViewModela.
            CurrentViewModel = homeVM;

            NavigationCommand = new DelegateCommand<string>(OnNavigate);
        }

        private void OnNavigate(string navPath)
        {
            switch (navPath)
            {
                case "other":
                    CurrentViewModel = otherVM;
                    break;
                case "home":
                    CurrentViewModel = homeVM;
                    break;
            }
        }

        private object _currentViewModel;
        public object CurrentViewModel
        {
            get { return _currentViewModel; }
            set
            {
                if (_currentViewModel != value)
                {
                    _currentViewModel = value;
                    OnPropertyChanged();
                }
            }
        }


        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
          PropertyChanged(this, new propertyChangedEventArgs(propertyName));
        }
        #endregion
    }

Where DelegateCommand you can make yours,check how to make RelayCommand(and generic one) or use PRISM that have it's own DelegateCommand. But if you want to use PRISM, it allready has navigation to regions, that can solve many problems. Check videos from Brian Lagunas.

在你可以做你的委托的时候,检查一下如何制作RelayCommand(和通用的)或者使用有自己授权的PRISM。但是如果你想要使用PRISM,那么allready就有区域导航,这可以解决很多问题。查看Brian Lagunas的视频。

EDIT: This is to show/hide grid. In your mainWindow where you set that EmergencyInfo u can show/hide that grid this way.

编辑:这是显示/隐藏网格。在你的主窗口中,你设置的紧急信息u可以这样显示/隐藏网格。

in your MainViewViewModel make a bool property IsVisible

在mainviewmodel中,使bool属性可见

private bool _isVisible;
        public bool IsVisible
        {
            get { return _isVisible; }
            set
            {
                _isVisible = value;
                OnPropertyChanged();
            }
        }

in your MainView.Resources set key to BooleanToVisibilityConverter something like:

MainView。参考资料设置了BooleanToVisibilityConverter的键,如下所示:

 <BooleanToVisibilityConverter x:Key="Show"/>

and your grid that you want to show/hide set visibility:

以及您想要显示/隐藏设置可见性的网格:

<Grid x:Name="SomeGridName"
              Grid.Row="1"
              Grid.Colum="1"
              Visibility="{Binding IsVisible,Converter={StaticResource Show}}">

And finally set that IsVisible property to some ToggleButton, just to switch between true/false

最后将IsVisible属性设置为ToggleButton,在true/false之间切换

 <ToggleButton IsChecked="{Binding IsVisible}"
                              Content="ShowGrid" />

This way, you show/hide that grid part based on IsVisible, and you control that visibility onClick. Hope that helps.

通过这种方式,您可以显示/隐藏基于IsVisible的网格部分,并控制该可见性onClick。希望有帮助。

#2


2  

Inside your Window xaml:

在你窗口xaml:

 <ContentPresenter Content="{Binding Control}"></ContentPresenter>

In this way, your ViewModel must contain a Control property.

这样,您的ViewModel必须包含一个控件属性。

Add your ViewModel to DataContext of Window. (For example in window constructor, this.Datacontext = new ViewModel();)

将ViewModel添加到窗口的DataContext中。(例如在窗口构造函数中,这个。Datacontext = new ViewModel();

Or another way with interfaces:

或者是另一种接口方式:

 public interface IWindowView
 {
        IUserControlKeeper ViewModel { get; set; }
 }

 public interface IUserControlKeeper 
 {
        UserControl Control { get; set; }
 }


 public partial class YourWindow : IViewWindow
 {
        public YourWindow()
        {
            InitializeComponent();
        }

        public IUserControlKeeper ViewModel
        {
            get
            {
                return (IUserControlKeeper)DataContext;
            }

            set
            {
                DataContext = value;
            }
        }
  }

(In this way, initialize your window where you want to use. Service?)

(用这种方法,初始化您想要使用的窗口。服务吗?)

private IViewWindow _window;
private IViewWindow Window  //or public...
{
  get{
       if(_window==null)
       {
           _window = new YourWindow();
           _window.ViewModel = new YourViewModel();
       }
       return _window;
     }
}

Open your window with one of your UserControls:

使用您的用户控件之一打开您的窗口:

  void ShowWindowWithControl(object ControlView, INotifyPropertyChanged ControlViewModel, bool ShowAsDialog)
  {
        if(ControlView is UserControl)
        {       //with interface: Window.ViewModel.Control = ...
               (Window.DataContext as YourViewModel).Control = (UserControl)ControlView;

               if (ControlViewModel != null)
                   (Window.DataContext as YourViewModel).Control.DataContext = ControlViewModel;

               if (ShowAsDialog) //with interface use cast to call the show...
                   Window.ShowDialog();
               else
                   Window.Show();

         }
  }

#3


0  

What you can do is, place the Emergency usercontrol inside MainGrid and control its visibility through Model property.

您可以做的是,将紧急用户控件放置在MainGrid中,并通过模型属性控制其可见性。

    <Canvas Background="DarkGray">
    <!--Main Grid-->
    <Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
          HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <StackPanel Background="Black">                
            <Grid Background="#253341">
                <Grid.RowDefinitions>
                    <RowDefinition Height="5"/>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="5"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="264"/>
                </Grid.ColumnDefinitions>

                <Grid Grid.Row="1" Grid.Column="1">
                    <Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
                            Command="{Binding ShowEmergencyPanel}">
                        <Image Source="{StaticResource EmergencyImg}" />
                    </Button>
                </Grid>


            </Grid>
        </StackPanel>

        <Grid Name="EmergencyPanel">
            <view1:EmergencyInfo x:Name="EmergencyInfoPanel" Visibility={Binding IsEmergencyPanelVisible,Converter={StaticResource BoolToVisibilityConverter}} DataContext={Binding} />
        </Grid>
    </Grid>
    <!--Main Grid-->
</Canvas>

By setting proper background to Grid that holding usercontrol, you can achieve modal window like effect. And you need to have IsEmergencyPanelVisible(default value is false) is your Model, and this property should be changed to true on your button click command.

通过在网格中设置合适的背景来控制用户控件,可以实现模式窗口效果。您需要让isemergency cypanelvisible(默认值为false)是您的模型,这个属性应该在您的按钮单击命令中将其更改为true。

Note : I guess you are familiar with converters, so i included Converters in my solution.

注意:我想你对转换器很熟悉,所以我在解决方案中加入了转换器。