Reputation: 4453
In XAML/WPF I have main window which contains Frame where I intend to put one of the user controls for given view of application.
<Window x:Class="MyApp.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:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot">
<Frame Source="Main/MainUserControl.xaml" Name="Main" />
</Grid>
</Window>
Now I want to navigate this Frame to other source inside MainUserControl:
<UserControl x:Class="MyApp.View.MainMenu.MainMenuUserControl"
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:lex="http://wpflocalizeextension.codeplex.com"
xmlns:command="clr-namespace:MyApp.Command"
mc:Ignorable="d"
Style="{StaticResource Localizable}"
DataContext="{Binding MainMenu, Source={StaticResource Locator}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Content="{lex:Loc About}" FontSize="28" Grid.Row="1" Command="NavigationCommands.GoToPage" CommandParameter="/Menu/AboutUserControl.xaml" />
</Grid>
</UserControl>
But the navigation button About remains inactive during execution. I verified correctly that /Menu/AboutUserControl.xaml exists.
I'm obviously doing something wrong. How can I navigate owning window's frame from within user control? Preferably via XAML?
Upvotes: 0
Views: 1096
Reputation: 374
I assume you are using an MVVM framework. (I have added the critical elements here in case you aren't).
Your MainWindow.xaml should use an "ItemsControl" instead of a "Frame". A frame can work, but a better way is to use the ItemsControl like so:
<!-- Main Frame -->
<Grid Grid.Column="1" Margin="10" Name="MainWindowFrameContent">
<ItemsControl ItemsSource="{Binding Path=MainWindowFrameContent}" >
<!-- This controls the height automatically of the user control -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
In the constructor of my MainWindow.cs, I set the DataContext of the window to the MainViewModel:
using myProject.ViewModel;
public partial class MainWindow : Window
{
MainViewModel mMainViewModel;
public MainWindow()
{
InitializeComponent();
// Initialize MainViewModel and set data context to the VM
mMainViewModel = new MainViewModel();
DataContext = mMainViewModel;
}
}
(I'm not sure if this next part HAS TO be an observable collection, but I have implemented it as such and it seems to work well. The only downside is that I need to manually clear the ItemsControl before adding a new UserControl)
My MainViewModel implements the binding called "MainWindowFrameContent". All of my user controls are initialized within the MainViewModel.cs code. I have an additional ViewModel for each UserControl and assign the DataContext of the UserControl to the individual ViewModel before displaying the UserControl to the main window.
My MainViewModel.cs:
public class MainViewModel : ObservableObject
{
public MainViewModel()
{
}
// This handles adding framework (UI) elements to the main window frame
ObservableCollection<FrameworkElement> _MainWindowFrameContent = new ObservableCollection<FrameworkElement>();
public ObservableCollection<FrameworkElement> MainWindowFrameContent
{
get
{
return _MainWindowFrameContent;
}
set
{
_MainWindowFrameContent = value;
RaisePropertyChangedEvent("MainWindowFrameContent");
}
}
// This handles opening a generic user control on the main window
// The ICommand implementation allows me to bind the command of a button on the main window to displaying a specific page
public ICommand MainWindowDataEntryView
{
get
{
return new DelegateCommand(_MainWindowDataEntryView);
}
}
void _MainWindowDataEntryView(object obj)
{
DataEntryVM wDataEntryVM = new DataEntryVM();
DataEntryView wDataEntryView = new DataEntryView();
wDataEntryView.DataContext = wDataEntryVM;
MainWindowFrameContent.Clear();
MainWindowFrameContent.Add(wDataEntryView);
}
}
Then you need to make sure you have an ObservableObject.cs as part of your project:
using System.ComponentModel;
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And you need a DelegateCommand.cs class as part of your project:
using System;
using System.Windows.Input;
public class DelegateCommand : ICommand
{
private readonly Action<object> _action;
public DelegateCommand(Action<object> action)
{
_action = action;
}
public void Execute(object parameter)
{
_action(parameter);
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged { add { } remove { } }
#pragma warning restore 67
}
So, it's a bit of a lengthy explanation, but once you have the previous items set up, you can add a bunch of buttons to your MainWindow.xaml, bind each button to a command that adds a new UserControl to your ItemsControl. When your UserControl displays, you can add controls as you would like and use them.
I hope this helps!
Upvotes: 1