简体   繁体   中英

WPF ContentControl binding of dynamic content does not works properly

Again, I apologize in advance for any mistakes, English is not my native. I'm making an MVVM app, and I want to dynamically change views using ContentControl in the MainWindow, here is a necessary part of the code to understand:

Firstly, views: MainWindow.xaml

<Window x:Class="Vernam.MainWindow"
        x:Name="MainWindowID"
        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:local="clr-namespace:Vernam"
        xmlns:viewModel="clr-namespace:Vernam.ViewModels"
        mc:Ignorable="d"
        Height="600"
        Width="900"
        ...>

    <Window.Resources>
        <viewModel:MainViewModel x:Key="vm"></viewModel:MainViewModel>
    </Window.Resources>
                  ...
          <Grid>
            <Grid Grid.Row="0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"></ColumnDefinition>
                    <ColumnDefinition Width="40"></ColumnDefinition>
                    <ColumnDefinition Width="454"></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>

                ...
                <StackPanel Grid.Column="2" Orientation="Horizontal"
                            DataContext="{Binding Source={StaticResource vm}}">
                    <RadioButton x:Name="CorrRadioButton"
                        Content="Correspondences"
                        Width="176"
                        Foreground="White"
                        FontSize="18"
                        Style="{StaticResource HeadButtonTheme}"
                        GroupName="Head"
                        IsChecked="True"
                        Command="{Binding Path=CorrCommand}"/>
                    <RadioButton x:Name="ProfileRadioButton"
                        Content="Profile"
                        Width="89"
                        Foreground="White"
                        FontSize="18"
                        Style="{StaticResource HeadButtonTheme}"
                        GroupName="Head"
                        Command="{Binding Path=ProfileCommand}"/>
                </StackPanel>

               ...

            </Grid>
            <ContentControl Grid.Row="1"
                            Content="{Binding CurrentView}"/>
        </Grid>
    </Border>
    </Window>

and MainWindow.xaml.cs:

public partial class MainWindow : Window
    {
        bool isMinimized = false;
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }

Two views, that I want to be shown in MainWindow:

CorrespondensesView.xaml

<UserControl x:Class="Vernam.Views.CorrespondensesView"
             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:Vernam.Views"
             mc:Ignorable="d"
             Height="540"
             Width="900">
    <Grid Background="#035251">
        ...
    </Grid>
</UserControl>

CorrespondensesView.xaml.cs

public partial class CorrespondensesView : UserControl
    {
        public CorrespondensesView()
        {
            InitializeComponent();
        }
    }

ProfileView.xaml:

<UserControl x:Class="Vernam.Views.ProfileView"
             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:Vernam.Views"
             mc:Ignorable="d" 
             Height="540"
             Width="900">
    <Grid Background="#035251">
        ...
    </Grid>
</UserControl>

ProfileView.xaml.cs:

public partial class ProfileView : UserControl
    {
        public ProfileView()
        {
            InitializeComponent();
        }
    }

Here are MainWindow view model

namespace Vernam.ViewModels
{
    public class MainViewModel : ObservableObject
    {
        private RelayCommand corrCommand;
        private RelayCommand profileCommand;
        private object currentView;

        public CorrespondensesViewModel CorrVM { get; set; }
        public ProfileViewModel ProfileVM { get; set; }

        public object CurrentView
        {
            get { return currentView; }
            set
            {
                currentView = value;
                this.OnPropertyChanged("CurrentView");
            }
        }
        public RelayCommand CorrCommand
        {
            get
            {
                return corrCommand ??
                    (corrCommand = new RelayCommand(obj =>
                    {
                        CurrentView = CorrVM;
                    }));
            }
        }

        public RelayCommand ProfileCommand
        {
            get
            {
                return profileCommand ??
                    (profileCommand = new RelayCommand(obj =>
                    {
                        CurrentView = ProfileVM;
                    }));
            }
        }
        public MainViewModel()
        {
            CorrVM = new CorrespondensesViewModel();
            ProfileVM = new ProfileViewModel();
            
            CurrentView = CorrVM;
        }
    }
}

CorrespondencesViewModel and ProfileViewModel are empty.

And, finally, App.xaml

<Application x:Class="Vernam.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Vernam"
             xmlns:viewModel="clr-namespace:Vernam.ViewModels"
             xmlns:view="clr-namespace:Vernam.Views"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
             ...
            <DataTemplate DataType="{x:Type viewModel:CorrespondensesViewModel}">
                <view:CorrespondensesView/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type viewModel:ProfileViewModel}">
                <view:ProfileView/>
            </DataTemplate>
        </ResourceDictionary>
    </Application.Resources>
</Application>

You may need to look at the ObservableObject class:

public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

Then I run the app, I can actually see view, that I assign to CurrentView in MainViewModel constructor:

        public MainViewModel()
        {
            CorrVM = new CorrespondensesViewModel();
            ProfileVM = new ProfileViewModel();
            
            CurrentView = CorrVM;
        }

If I assign CorrVM or ProfileVM to CurrentView, I actually see CorrespondensesView or ProfileView, but I can't change view dynamically:

RadioButton Command binding works properly, CurrentView is reassigned every time I click on the corresponding button, but I can't see any changes in MainWindow.

So I think the problem is in the binding, do you have any ideas how to fix it?

UPD: Get section of this property is called only during the initialization, so the problem is definitely with binding.

public ObservableObject CurrentView
        {
            get { return currentView; }
            set
            {
                currentView = value;
                this.OnPropertyChanged("CurrentView");
            }
        }

Tried to use different binding modes and update triggers, but to no avail.

<ContentControl Grid.Row="1"
                            Content="{Binding Path=CurrentView, UpdateSourceTrigger=PropertyChanged,  Mode=OneWay}"/>

Try the following:

Create the following folders:

  • Model
  • Theme
  • View
  • ViewModel

在此处输入图像描述

Add the files below to the appropriate folder (see image above).

HeadButtonTheme.xaml (ResourceDictionary)

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style BasedOn="{StaticResource {x:Type ToggleButton}}"
           TargetType="{x:Type RadioButton}"
           x:Key="HeadButtonTheme">
        <Style.Setters>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="RadioButton">
                        <Grid VerticalAlignment="Stretch"
                             HorizontalAlignment="Stretch"
                             Background="{TemplateBinding Background}">

                            <TextBlock Text="{TemplateBinding Property=Content}"
                                       VerticalAlignment="Center" 
                                       Margin="5, 0, 0, 0"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>

            <Setter Property="Background" Value="Transparent" />
            <Setter Property="BorderThickness" Value="0" />
        </Style.Setters>

        <Style.Triggers>
            <Trigger Property="IsChecked" Value="True">
                <Setter Property="Background" Value="#22202f" />
            </Trigger>
        </Style.Triggers>

    </Style>

</ResourceDictionary>

ObservableObject.cs (Class)

using System;
using System.ComponentModel;
using System.Windows.Input;
using System.Runtime.CompilerServices;

namespace Vernam
{
    class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;


        protected void OnPropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }
}

RelayCommand.cs (Class)

using System;
using System.Windows.Input;

namespace Vernam
{
    class RelayCommand : ICommand
    {
        private Action<object> _execute;
        private Func<object, bool> _canExecute;

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested += value; }
        }

        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }
    }
}

CorrespondencesViewModel.cs (Class)

namespace Vernam.ViewModel
{
    class CorrespondencesViewModel
    {
    }
}

CorrespondencesView.xaml (UserControl)

<UserControl x:Class="Vernam.View.CorrespondencesView"
             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:Vernam.View"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">

    <Grid>
        <TextBlock Text="Correspondences" Foreground="Red"/>
    </Grid>
</UserControl>

ProfileViewModel.cs (Class)

namespace Vernam.ViewModel
{
    class ProfileViewModel
    {
    }
}

ProfileView.xaml (UserControl)

<UserControl x:Class="Vernam.View.ProfileView"
             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:Vernam.View"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">

    <Grid>
        <TextBlock Text="Profile" Foreground="Red"/>
    </Grid>
</UserControl>

MainViewModel.cs (Class)

namespace Vernam.ViewModel
{
    class MainViewModel : ObservableObject
    {
        public RelayCommand CorrCommand { get; set; }
        public RelayCommand ProfileCommand { get; set; }

        public CorrespondencesViewModel CorrVM { get; set; }
        public ProfileViewModel ProfileVM { get; set; }

        private object currentView;

        public object CurrentView
        {
            get { return currentView; }
            set
            {
                currentView = value;
                OnPropertyChanged("CurrentView");
            }
        }

        public MainViewModel()
        {
            //create new instance
            CorrVM = new CorrespondencesViewModel();
            ProfileVM = new ProfileViewModel();

            CorrCommand = new RelayCommand(obj =>
            {
                CurrentView = CorrVM;
            });

            ProfileCommand = new RelayCommand(obj =>
            {
                CurrentView = ProfileVM;
            });


            //set default view
            CurrentView = CorrVM;
        }
    }
}

App.xaml

<Application x:Class="Vernam.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Vernam"
             xmlns:viewModel="clr-namespace:Vernam.ViewModel"
             xmlns:view="clr-namespace:Vernam.View"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Theme\HeadButtonTheme.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <DataTemplate DataType="{x:Type viewModel:CorrespondencesViewModel}">
                <view:CorrespondencesView />
            </DataTemplate>

            <DataTemplate DataType="{x:Type viewModel:ProfileViewModel}">
                <view:ProfileView />
            </DataTemplate>
                          
        </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml

<Window x:Class="Vernam.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:local="clr-namespace:Vernam"
        xmlns:viewModel="clr-namespace:Vernam.ViewModel"
        xmlns:view="clr-namespace:Vernam.View"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <viewModel:MainViewModel x:Key="vm"></viewModel:MainViewModel>
    </Window.Resources>
    
    <Border Background="LightGray">
        <Grid DataContext="{Binding Source={StaticResource vm}}">

            <Grid.RowDefinitions>
                <RowDefinition Height="50" />
                <RowDefinition />
            </Grid.RowDefinitions>
            
            <Grid Grid.Row="0">

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="350" />
                    <ColumnDefinition Width="350" />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                
                <StackPanel Orientation="Horizontal">
                    <RadioButton x:Name="CorrRadioButton"
                        Content="Correspondences"
                        Width="176"
                        Foreground="White"
                        FontSize="18"
                        Style="{StaticResource HeadButtonTheme}"
                        GroupName="Head"
                        IsChecked="True"
                        Command="{Binding CorrCommand}"/>

                    <RadioButton x:Name="ProfileRadioButton"
                        Content="Profile"
                        Width="89"
                        Foreground="White"
                        FontSize="18"
                        Style="{StaticResource HeadButtonTheme}"
                        GroupName="Head"
                        Command="{Binding ProfileCommand}"/>
                </StackPanel>
            </Grid>
            
            <ContentControl Grid.Row="1" Grid.Column="0"
                            Content="{Binding CurrentView}"  />
        </Grid>
    </Border>
</Window>

Resources :

I believe that because you are using two different view model instances for the radio buttons and the content view in MainWindow.

Here you use an instance from the window resource.

 <StackPanel Grid.Column="2" Orientation="Horizontal"
                            DataContext="{Binding Source={StaticResource vm}}">

Here you use an instance from the data context of MainWindow.

 <ContentControl Grid.Row="1" Grid.Column="0"
                        Content="{Binding CurrentView}"  />

So the fixing is quite simple, use only one of them.

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