« Prism navigation with tabcontrol » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
Ligne 246 : Ligne 246 :


     return canClose;
     return canClose;
}
</filebox>
<filebox fn='ViewAViewModel.cs'>
public sealed class ViewAViewModel : BindableBase, INavigationAware, IConfirmNavigationRequest
{
    // ...
    public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
    {
        continuationCallback(false);  // forbid to close
    }
}
}
</filebox>
</filebox>

Version du 18 octobre 2021 à 14:29

Navigation

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>
Xaml.svg
<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
    }
}