« Prism navigation with tabcontrol » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
Ligne 65 : | Ligne 65 : | ||
</filebox> | </filebox> | ||
== | == Views and ViewModels == | ||
<filebox fn=' | <filebox fn='ItemsListView.xaml'> | ||
<UserControl x:Class="NavigationWPF.Views. | <UserControl x:Class="NavigationWPF.Views.ItemsListView" | ||
xmlns:mvvm="http://prismlibrary.com/" | xmlns:mvvm="http://prismlibrary.com/" | ||
mvvm:ViewModelLocator.AutoWireViewModel="True"> | mvvm:ViewModelLocator.AutoWireViewModel="True"> | ||
< | <ListBox ItemsSource="{Binding Items}"> | ||
<TextBlock Text=" | <ListBox.ItemTemplate> | ||
<DataTemplate> | |||
<TextBlock Text="{Binding Name}"> | |||
<TextBlock.InputBindings> | |||
</ | <MouseBinding MouseAction="LeftDoubleClick" | ||
Command="{Binding DataContext.OpenItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}" | |||
CommandParameter="{Binding}" /> | |||
</TextBlock.InputBindings> | |||
</TextBlock> | |||
</DataTemplate> | |||
</ListBox.ItemTemplate> | |||
</ListBox> | |||
</UserControl> | </UserControl> | ||
</filebox> | </filebox> | ||
<filebox fn=' | <filebox fn='ItemsListViewModel.cs'> | ||
public class | public sealed class ItemsListViewModel : BindableBase, IConfirmNavigationRequest | ||
{ | { | ||
private string | private readonly IItemRepository _itemRepository; | ||
private readonly IRegionManager _regionManager; | |||
private string _title; | |||
public AdvancedObservableCollection<Item> Items { get; } | |||
public string Title | public string Title | ||
{ | { | ||
get => this. | get => this._title; | ||
set => SetProperty(ref this. | set => SetProperty(ref this._title, value); | ||
} | } | ||
public | public ICommand OpenItemCommand { get; } | ||
public ItemsListViewModel(IItemRepository itemRepository, IRegionManager regionManager) | |||
{ | { | ||
Title = " | this._itemRepository = itemRepository; | ||
this._regionManager = regionManager; | |||
Items = new AdvancedObservableCollection<Item>(); | |||
Title = "Items"; | |||
OpenItemCommand = new DelegateCommand<Item>(OpenItem); | |||
var items = this._itemRepository.GetItems(); | |||
Items.AddRange(items); | |||
} | } | ||
public bool IsNavigationTarget(NavigationContext navigationContext) => true; | private void OpenItem(Item item) | ||
{ | |||
var navigationParameters = new NavigationParameters | |||
{ | |||
{ "item", item } | |||
}; | |||
// pass the item while navigating to the ItemView | |||
this._regionManager.RequestNavigate("TabRegion", "ItemView", navigationParameters); | |||
} | |||
public bool IsNavigationTarget(NavigationContext navigationContext) => true; // always reuse the TabItem | |||
public void | public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) | ||
{ } | { | ||
// allow to navigate from this TabItem but not to close it (navigationContext.Uri == null) | |||
continuationCallback(navigationContext.Uri != null); | |||
} | |||
public void | public void OnNavigatedFrom(NavigationContext navigationContext) { } | ||
{ } | public void OnNavigatedTo(NavigationContext navigationContext) { } | ||
} | } | ||
</filebox> | </filebox> |
Version du 20 octobre 2021 à 08:17
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"> <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="ItemsListView"> Items </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; } public MainWindowViewModel(IRegionManager regionManager) { _regionManager = regionManager; NavigateCommand = new DelegateCommand<string>(Navigate); } // ItemsListView is a command parameter 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); } } |
App.xaml.cs |
protected override void RegisterTypes(IContainerRegistry containerRegistry) { // register the views to allow RequestNavigate with the view name containerRegistry.RegisterForNavigation<ItemsListView>(); containerRegistry.RegisterForNavigation<ItemView>(); } |
Views and ViewModels
ItemsListView.xaml |
<UserControl x:Class="NavigationWPF.Views.ItemsListView" xmlns:mvvm="http://prismlibrary.com/" mvvm:ViewModelLocator.AutoWireViewModel="True"> <ListBox ItemsSource="{Binding Items}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}"> <TextBlock.InputBindings> <MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.OpenItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}" CommandParameter="{Binding}" /> </TextBlock.InputBindings> </TextBlock> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </UserControl> |
ItemsListViewModel.cs |
public sealed class ItemsListViewModel : BindableBase, IConfirmNavigationRequest { private readonly IItemRepository _itemRepository; private readonly IRegionManager _regionManager; private string _title; public AdvancedObservableCollection<Item> Items { get; } public string Title { get => this._title; set => SetProperty(ref this._title, value); } public ICommand OpenItemCommand { get; } public ItemsListViewModel(IItemRepository itemRepository, IRegionManager regionManager) { this._itemRepository = itemRepository; this._regionManager = regionManager; Items = new AdvancedObservableCollection<Item>(); Title = "Items"; OpenItemCommand = new DelegateCommand<Item>(OpenItem); var items = this._itemRepository.GetItems(); Items.AddRange(items); } private void OpenItem(Item item) { var navigationParameters = new NavigationParameters { { "item", item } }; // pass the item while navigating to the ItemView this._regionManager.RequestNavigate("TabRegion", "ItemView", navigationParameters); } public bool IsNavigationTarget(NavigationContext navigationContext) => true; // always reuse the TabItem public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) { // allow to navigate from this TabItem but not to close it (navigationContext.Uri == null) continuationCallback(navigationContext.Uri != null); } 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) { } } |