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 DataGrid
s as a user changes tabs in a Fluent:Ribbon
control. I would like to reuse the views, as the DataGrid
s 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.
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));
}
}
}
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>