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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42709379/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 14:02:23  来源:igfitidea点击:

How to display user control within the main window in WPF using MVVM

c#.netwpfxamlmvvm

提问by Juniuz

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)

这是另一个窗口(用户控件 - EmergencyInfo.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 ShowEmergencyPanelin button EmergencyButton to show EmergencyInfo when this button is click.

我想使用 MVVM 模式来实现这种行为。我ShowEmergencyPanel在按钮 EmergencyButton 中设置了绑定,以在单击此按钮时显示 EmergencyInfo。

Any help is greatly appreciated.

任何帮助是极大的赞赏。

回答by ivica.moke

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.

你为什么不做导航,这样的事情。为将被注入的内容创建部分,以及您期望的任何类型的对象将其放在 DataTemplate 中的 Windows.Resources 中。

In main windo xaml

在主窗口 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.

您可以在哪里制作 DelegateCommand,检查如何制作 RelayCommand(和通用的)或使用具有自己的 DelegateCommand 的 PRISM。但是如果你要使用PRISM,它已经有区域导航,可以解决很多问题。查看 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.

编辑:这是为了显示/隐藏网格。在您设置紧急信息的主窗口中,您可以通过这种方式显示/隐藏该网格。

in your MainViewViewModel make a bool property IsVisible

在您的 MainViewViewModel 中创建一个 bool 属性 IsVisible

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

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

在您的 MainView.Resources 中将键设置为 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,只是为了在真/假之间切换

 <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 显示/隐藏该网格部分,并在单击时控制该可见性。希望有帮助。

回答by Péter Hidvégi

Inside your Window xaml:

在您的 Window xaml 中:

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

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

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

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

将您的 ViewModel 添加到 Window 的 DataContext。(例如在窗口构造函数中,this.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();

         }
  }

回答by WPFUser

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

您可以做的是,将 Emergency usercontrol 放置在 MainGrid 中,并通过 Model 属性控制其可见性。

    <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.

通过为持有用户控件的 Grid 设置适当的背景,您可以实现类似模态窗口的效果。并且您需要将 IsEmergencyPanelVisible(默认值为 false)作为您的模型,并且在您的按钮单击命令上应将此属性更改为 true。

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

注意:我猜您对转换器很熟悉,所以我在我的解决方案中包含了转换器。