|
|
Ligne 1 : |
Ligne 1 : |
| [[Category:WPF]] | | [[Category:WPF]] |
| = Navigation = | | = Navigation = |
| | Injecting a view in the TabControl region will automatically create a new TabItem or select an existing TabItem. |
| <filebox fn='MainWindow.xaml'> | | <filebox fn='MainWindow.xaml'> |
| <Window x:Class="NavigationWPF.Views.MainWindow" | | <Window x:Class="NavigationWPF.Views.MainWindow" |
Version du 20 octobre 2021 à 08:06
Navigation
Injecting a view in the TabControl region will automatically create a new TabItem or select an existing TabItem.
MainWindow.xaml
|
<Window x:Class="NavigationWPF.Views.MainWindow"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding DataContext.Title}" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding NavigateCommand}"
CommandParameter="ViewA">
Navigate A
</Button>
<Button Command="{Binding NavigateCommand}"
CommandParameter="ViewB">
Navigate B
</Button>
</StackPanel>
<TabControl Grid.Row="1"
prism:RegionManager.RegionName="TabRegion" />
</Grid>
</Window>
|
MainWindowViewModel.cs
|
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
public DelegateCommand<string> NavigateCommand { get; set; }
public MainWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
// ViewA and ViewB are command parameters passed as navigationPath
void Navigate(string navigationPath)
{
// open/reuse a tab and inject a view that has the same name as the navigationPath
_regionManager.RequestNavigate("TabRegion", navigationPath);
}
}
|
Navigation configuration
App.xaml.cs
|
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// register the views to allow RequestNavigate with the view name
containerRegistry.RegisterForNavigation<ViewA>();
containerRegistry.RegisterForNavigation<ViewB>();
}
|
View to inject
ViewA.xaml
|
<UserControl x:Class="NavigationWPF.Views.ViewA"
xmlns:mvvm="http://prismlibrary.com/"
mvvm:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBlock Text="View A"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="48" />
</Grid>
</UserControl>
|
ViewAViewModel.cs
|
public class ViewAViewModel : BindableBase, INavigationAware
{
private string title;
public string Title
{
get => this.title;
set => SetProperty(ref this.title, value);
}
public ViewAViewModel()
{
Title = "View A";
}
public bool IsNavigationTarget(NavigationContext navigationContext) => true;
public void OnNavigatedFrom(NavigationContext navigationContext)
{ }
public void OnNavigatedTo(NavigationContext navigationContext)
{ }
}
|
Closing Tab items
MainWindow.xaml
|
<Window.Resources>
<Style TargetType="TabItem">
<!-- redefine the header template with a button -->
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Right"
Style="{StaticResource TabCloseButton}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Click">
<local:CloseTabAction />
</b:EventTrigger>
</b:Interaction.Triggers>
</Button>
<ContentControl Content="{Binding}"
VerticalAlignment="Center" />
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
|
|
<Style x:Key="TabCloseButton"
TargetType="Button"
BasedOn="Button">
<Setter Property="Height" Value="14" />
<Setter Property="Foreground" Value="DarkGray" />
<Setter Property="Margin" Value="4,0,-4,0" />
<Setter Property="Template" Value="{StaticResource CrossButton}" />
</Style>
<ControlTemplate x:Key="CrossButton"
TargetType="{x:Type Button}">
<Grid>
<Rectangle x:Name="BackgroundRectangle" />
<Path x:Name="ButtonPath"
Margin="3"
Stroke="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}"
StrokeThickness="1.5"
StrokeStartLineCap="Square"
StrokeEndLineCap="Square"
Stretch="Uniform"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="0,0">
<LineSegment Point="25,25" />
</PathFigure>
<PathFigure StartPoint="0,25">
<LineSegment Point="25,0" />
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="BackgroundRectangle" Property="Fill" Value="LightGray" />
<Setter TargetName="ButtonPath" Property="Stroke" Value="White" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="BackgroundRectangle" Property="Fill" Value="Gray" />
<Setter TargetName="ButtonPath" Property="Stroke" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
|
CloseTabAction
|
public sealed class CloseTabAction : TriggerAction<Button>
{
protected override void Invoke(object parameter)
{
var args = parameter as RoutedEventArgs;
if (args == null)
return;
var tabItem = VisualTreeHelperPlus.GetParent<TabItem>(args.OriginalSource as DependencyObject);
if (tabItem == null)
return;
var tabControl = VisualTreeHelperPlus.GetParent<TabControl>(tabItem);
if (tabControl == null)
return;
var region = RegionManager.GetObservableRegion(tabControl).Value;
if (region == null)
return;
if (region.Views.Contains(tabItem.Content))
region.Remove(tabItem.Content); // remove the view from the region
// Prism will remove automatically the containing element (TabItem) from the hosting control (TabControl)
}
}
|
Preventing close
CloseTabAction.cs
|
protected override void Invoke(object parameter)
{
// ...
RemoveItemFromRegion(tabItem.Content, region);
}
private void RemoveItemFromRegion(object item, IRegion region)
{
var navigationContext = new NavigationContext(region.NavigationService, null);
if (CanRemove(item, navigationContext))
region.Remove(item);
}
private bool CanRemove(object item, NavigationContext navigationContext)
{
var canClose = true;
// check if the VM implements IConfirmNavigationRequest
if (item is FrameworkElement frameworkElement &&
frameworkElement.DataContext is IConfirmNavigationRequest confirmNavigationRequest)
{
confirmNavigationRequest.ConfirmNavigationRequest(navigationContext, x => canClose = x);
}
return canClose;
}
|
ViewAViewModel.cs
|
public sealed class ViewAViewModel : BindableBase, INavigationAware, IConfirmNavigationRequest
{
// ...
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
continuationCallback(false); // forbid to close
}
}
|
Knowing when a TabItem is closing
CloseTabAction.cs
|
private void RemoveItemFromRegion(object item, IRegion region)
{
var navigationContext = new NavigationContext(region.NavigationService, null);
if (CanRemove(item, navigationContext))
{
OnNavigatedFrom(item, navigationContext);
region.Remove(item);
}
}
private void OnNavigatedFrom(object item, NavigationContext navigationContext)
{
if (item is FrameworkElement frameworkElement &&
frameworkElement.DataContext is INavigationAware navigationAwareDataContext)
{
navigationAwareDataContext.OnNavigatedFrom(navigationContext);
}
}
|
ViewAViewModel.cs
|
public sealed class ViewAViewModel : BindableBase, INavigationAware, IConfirmNavigationRequest
{
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
|