Search code examples
c#wpfxamlmvvm

UserControl not rendering in WPF ContentControl


I am new to WPF and MVVM. I've spent two days scrolling through a bunch of SO q&a's on this topic and I haven't had any success getting this to work. Which inevitably means it's something silly that I'm overlooking.

I am trying to use DataTemplate along with a ContentControl to switch between content DataGrids as a user changes tabs in a Fluent:Ribbon control. I would like to reuse the views, as the DataGrids contained therein can be expensive to fill.

I have these views/viewmodels:

  • MainWindow.xaml / MainWindowViewModel.cs - the main application window consisting of Fluent:RibbonWindow, Fluent:Ribbon and Fluent:StatusBar controls (some of which are removed in my code snippets below for clarity). This class contains a member property for tracking "current content" (CurrentViewModel) and member properties for command processing when user clicks on buttons in the Fluent:Ribbon. Other viewmodels are instantiated in this class as private members.
  • ProviderView.xaml / ProviderViewModel.cs - displays a list of "Providers" (for the purposes of this post, just an abstract concept). The view contains a UserControl that contains a DataGrid control. The DataGrid is bound to the public Providers property (a list of Provider objects) inside an instance of ProviderViewModel which is public property of MainWindowViewModel.

When I run the application, and also apparent in the designer, the ContentControl just contains a string ViewModel.ProviderViewModel, as if it has no idea what to do with the control, or I'm not treeing it properly.

enter image description here

MainWindow.xaml

<Fluent:RibbonWindow x:Class="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:vm="clr-namespace:ViewModel"
                     xmlns:local="clr-namespace:MyProgram"
                     xmlns:Fluent="urn:fluent-ribbon"
                     mc:Ignorable="d"
                     Width="800" 
                     Height="600"
                     Name="MainRibbonWindow"
                     Icon="{DynamicResource logo}">
    <Fluent:RibbonWindow.Resources>
        <DataTemplate x:Key="g_ProviderViewModel" DataType="{x:Type vm:ProviderViewModel}">
            <local:ProviderView/>
        </DataTemplate>
    </Fluent:RibbonWindow.Resources>
    <Fluent:RibbonWindow.DataContext><vm:MainWindowViewModel/></Fluent:RibbonWindow.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Fluent:Ribbon VerticalAlignment="Top"
                       IsDisplayOptionsButtonVisible="False"
                       Name="MainWindowRibbon"
                       SelectedTabChanged="MainWindowRibbon_SelectedTabChanged">

            <!--Tabs-->
            <Fluent:RibbonTabItem Header="Providers" Name="ProvidersTab">
                <Fluent:RibbonGroupBox Header="Options" Width="120">
                    <Fluent:Button Header="Refresh"
                                   Icon="{DynamicResource refresh}"
                                   Command="{Binding LoadProvidersCommand}"/>
                </Fluent:RibbonGroupBox>
            </Fluent:RibbonTabItem>            
        </Fluent:Ribbon>
        <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1">
            <ContentControl Content="{Binding CurrentViewModel}"/>
        </StackPanel>
    </Grid>
</Fluent:RibbonWindow>

MainWindowViewModel.cs


namespace MyProgram.ViewModel
{
    class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ViewModelBase _CurrentViewModel;
        public ViewModelBase CurrentViewModel
        {
            get => _CurrentViewModel;
            set
            {
                _CurrentViewModel = value;
                OnPropertyChanged("CurrentViewModel");
            }
        }

        public ProviderViewModel m_ProviderViewModel = new ProviderViewModel();
        private ProviderManifestViewModel m_ProviderManifestViewModel = new ProviderManifestViewModel();

        private ICommand _loadProvidersCommand;
        public ICommand LoadProvidersCommand
        {
            get
            {
                return _loadProvidersCommand ?? (_loadProvidersCommand = new AsyncRelayCommand(Command_LoadProviders, GlobalUiCanExecute));
            }
        }

        private AsyncRelayCommand<Guid> _loadProviderCommand;
        public AsyncRelayCommand<Guid> LoadProviderCommand
        {
            get
            {
                return _loadProviderCommand ?? (_loadProviderCommand = new AsyncRelayCommand<Guid>(Command_LoadProvider));
            }
        }
        #endregion

        public MainWindowViewModel()
        {
            CurrentViewModel = m_ProviderViewModel;
        }

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public void ShowProviderViewModel()
        {
            CurrentViewModel = m_ProviderViewModel;
        }

        private async Task Command_LoadProviders()
        {
            g_UiBusy = true;
            CurrentViewModel = m_ProviderViewModel;
            await m_ProviderViewModel.LoadProviders();
            g_UiBusy = false;
        }

        private async Task<MyProvider?> Command_LoadProvider(Guid Id)
        {
            if (!GlobalUiCanExecute())
            {
                return null;
            }
            g_UiBusy = true;
            var provider = await m_ProviderViewModel.LoadProvider(Id);
            g_UiBusy = false;
            return provider;
        }
    }
}

ProviderView.xaml

<UserControl x:Class="MyProgram.ProviderView"
             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:MyProgram.ViewModel"
             xmlns:Fluent="urn:fluent-ribbon"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             Name="ProviderViewControl">
    <UserControl.DataContext><local:MainWindowViewModel/></UserControl.DataContext>
    <Grid Name="ProvidersGrid">
        <DataGrid Name="ProvidersDataGrid"
                  IsReadOnly="true"
                  AutoGenerateColumns="false"
                  ItemsSource="{Binding m_ProviderViewModel.Providers}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Binding="{Binding Id}"/>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                <DataGridTextColumn Header="Source" Binding="{Binding Source}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>

ProviderViewModel.cs


namespace MyProgram.ViewModel
{
    class ProviderViewModel : ViewModelBase
    {
        private ObservableCollection<MyProvider> _providers;
        public ObservableCollection<MyProvider> Providers
        {
            get => _providers;
            set
            {
                _providers = value;
                OnPropertyChanged("Providers");
            }
        }

        public ProviderViewModel()
        {
            _providers = new ObservableCollection<MyProvider>();
        }

        public async Task LoadProviders()
        {
            var providers = await ProviderLoader.GetProviders();
            if (providers == null)
            {
                return;
            }
            providers.ForEach(f => Providers.Add(f));
        }
    }
}


Solution

  • Try this. Replace:

    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1">
        <ContentControl Content="{Binding CurrentViewModel}"/>
    </StackPanel>
    

    With:

    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1">
        <local:ProviderView DataContext="{Binding CurrentViewModel}"/>
    </StackPanel>
    

    Remove from ProviderView.xaml:

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