简体   繁体   中英

How to set DataContext of UserControl in C# MVVM

I am new to MVVM so please bare with me.

Current issue: I have a MainWindow with a ServerView UserControl inside. I bind the DataContext of the UserControl to the ServerViewModel. However when clicking the "Start Server"-button and "Stop Server"-button it will not trigger the logic in the ServerViewModel (the OnStartServer and OnStopServer methods). Where do I go wrong?

在此处输入图片说明

MainWindow.xaml

<Window x:Class="ServerGUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:views="clr-namespace:ServerGUI.Views"
        xmlns:vm="clr-namespace:ServerGUI.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>

    <Window.Resources>
        <DataTemplate DataType="{x:Type vm:ServerViewModel}">
            <views:ServerView />
        </DataTemplate>
    </Window.Resources>
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="3*" />
        </Grid.ColumnDefinitions>

        <views:ServerView Grid.Column="0" x:Name="ServerView" DataContext="{Binding ServerViewModel}" />

        <Label Grid.Column="1" Content="Test" />
    </Grid>

</Window>

MainWindow.xaml.cs

using ServerGUI.ViewModels;
using System.Windows;

namespace ServerGUI
{
    public partial class MainWindow : Window
    {
        public MainViewModel MainViewModel { get; set; }

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

MainViewModel.cs

namespace ServerGUI.ViewModels
{
    public class MainViewModel : BaseViewModel
    {
        public ServerViewModel ServerViewModel { get; set; } = new ServerViewModel();

        public MainViewModel() 
        { 
        
        }
    }
}

ServerView.xaml

<UserControl x:Class="ServerGUI.Views.ServerView"
             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:data="clr-namespace:ServerGUI.Models" 
             xmlns:vms="clr-namespace:ServerGUI.ViewModels" 
             d:DataContext="{d:DesignInstance Type=vms:ServerViewModel}"
             mc:Ignorable="d" 
             d:DesignHeight="110" d:DesignWidth="200" Background="White">

    <GroupBox DataContext="{Binding ServerViewModel}" Grid.Column="0" Header="Server Properties">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="30" />
                <RowDefinition Height="30" />
                <RowDefinition Height="30" />
            </Grid.RowDefinitions>

            <Button Grid.Row="0" Content="Start Server" Command="{Binding Path=StartServerCommand}" Margin="3 5 3 0" />
            <Button Grid.Row="1" Content="Stop Server" Command="{Binding Path=StopServerCommand}" Margin="3 5 3 0" />
            <Label Grid.Row="2" x:Name="lblServerStatus" Content="Server Status: Offline" />
        </Grid>
    </GroupBox>

</UserControl>

ServerView.xaml.cs

using System.Windows.Controls;

namespace ServerGUI.Views
{
    public partial class ServerView : UserControl
    {
        public ServerView()
        {
            InitializeComponent();
        }

    }
}

ServerViewModel.cs

using ServerGUI.Commands;
using ServerGUI.Models;
using System.Windows.Input;

namespace ServerGUI.ViewModels
{
    public class ServerViewModel : BaseViewModel
    {
        #region Fields and Properties
        private readonly ServerModel _serverModel;
        public bool IsRunning
        {
            get => _serverModel.IsRunning;
        }
        #endregion

        #region ICommands
        private readonly RelayCommand _startServerCommand;
        private readonly RelayCommand _stopServerCommand;
        public ICommand StartServerCommand => _startServerCommand;
        public ICommand StopServerCommand => _stopServerCommand;
        #endregion

        #region Constructor
        public ServerViewModel()
        {
            _serverModel = new ServerModel();
            _startServerCommand = new RelayCommand(OnStartServer, CanStartServer);
            _stopServerCommand = new RelayCommand(OnStopServer, CanStopServer);
        }
        #endregion

        #region Methods
        private void OnStartServer(object command)
        {
            _serverModel.IsRunning = true;
            _startServerCommand.InvokeCanExecuteChanged();
            _stopServerCommand.InvokeCanExecuteChanged();
        }

        private void OnStopServer(object command)
        {
            _serverModel.IsRunning = false;
            _startServerCommand.InvokeCanExecuteChanged();
            _stopServerCommand.InvokeCanExecuteChanged();
        }

        private bool CanStartServer(object command) => !IsRunning;
        private bool CanStopServer(object command) => IsRunning;

        #endregion
    }
}

BaseViewModel.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ServerGUI.ViewModels
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
        {
            if (!EqualityComparer<T>.Default.Equals(field, newValue))
            {
                field = newValue;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                return true;
            }

            return false;
        }
    }
}

RelayCommand.cs

using System;
using System.Windows.Input;

namespace ServerGUI.Commands
{
    class RelayCommand : ICommand
    {
        private readonly Action<object> _executeAction;
        private readonly Func<object, bool> _canExecuteAction;

        public event EventHandler CanExecuteChanged;

        public RelayCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
        {
            _executeAction = executeAction;
            _canExecuteAction = canExecuteAction;
        }

        public void Execute(object parameter) => _executeAction(parameter);
        public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;
        public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

ServerModel.cs

using System.ComponentModel;

namespace ServerGUI.Models
{
    public class ServerModel : INotifyPropertyChanged
    {
        private bool isRunning = default;

        public event PropertyChangedEventHandler PropertyChanged;

        public ServerModel()
        {
            IsRunning = false;
        }

        public bool IsRunning
        {
            get => isRunning;
            set
            {
                isRunning = value;
                RaisePropertyChanged(nameof(IsRunning));
            }
        }

        private void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

In ServerView.xaml your UserControl assumes that its DataContext is of type ServerViewModel , and then in your GroupBox you ask for property ServerViewModel of it, so it becomes smth like ServerViewModel.ServerViewModel which is null.
Remove DataContext assignment in GroupBox

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM