WPF中如何使用区域导航

1.创建一个Prism框架的项目并设计好数据源

User如下:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WPF练习17区域导航.Models
{
    public class User
    {
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string UserPwd { get; set; }
    }
}

UserData如下:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WPF练习17区域导航.Models
{
    public static class UserData
    {
        public static List<User> Users = new List<User>() {
            new User(){ UserId=1,UserName="张三1",UserPwd="123456"},
            new User(){ UserId=2,UserName="张三2",UserPwd="123456"}
        };
    }
}

2.设计好MainWindow.XAML页面 并创建四个UserControl的导航页面以及相应的ViewModel然后将视图注册进Ioc容器

MainWindow.XAML如下:

XML 复制代码
<Window x:Class="WPF练习17区域导航.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}"
        Height="350"
        Width="525"
        WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Grid Background="Blue">
            <WrapPanel VerticalAlignment="Center">
                <TextBlock VerticalAlignment="Center"
                           FontSize="30"
                           Foreground="White"
                           Text="XXX管理系统" />

                <Button Width="50"
                        Margin="10,0,0,0"
                        Command="{Binding BackCommand}"
                        Content="后退" />

                <Button Width="50"
                        Margin="10,0,0,0"
                        Command="{Binding ForwardCommand}"
                        Content="前进" />

            </WrapPanel>
        </Grid>

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="160" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>

            <StackPanel Background="Green">
                <Button Height="30"
                        Margin="0,10,0,0"
                        Command="{Binding TogglePage}"
                        CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                        Content="PageA" />
                <Button Height="30"
                        Margin="0,10,0,0"
                        Command="{Binding TogglePage}"
                        CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                        Content="PageB" />
                <Button Height="30"
                        Margin="0,10,0,0"
                        Command="{Binding TogglePage}"
                        CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                        Content="PageC" />
            </StackPanel>

            <Border Grid.Column="1"
                    Padding="10"
                    BorderBrush="Red"
                    BorderThickness="2">
                <ContentControl prism:RegionManager.RegionName="ContentRegion" />
            </Border>

        </Grid>
    </Grid>
</Window>

创建四个UserControl的导航页面:

将视图注册进Ioc容器:

3.四个页面设计好以后 写好MainWIndow对应的ViewModel代码

PageA如下:

XML 复制代码
<UserControl x:Class="WPF练习17区域导航.Views.Pages.PageA"
             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:local="clr-namespace:WPF练习17区域导航.Views.Pages"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>

        <StackPanel VerticalAlignment="Center">
            <TextBlock Text="查询条件区域" />
        </StackPanel>

        <DataGrid Grid.Row="1"
                  AutoGenerateColumns="False"
                  IsReadOnly="True"
                  ItemsSource="{Binding Users}">
            <DataGrid.Columns>
                <DataGridTextColumn Width="*"
                                    Binding="{Binding UserId}"
                                    Header="编号" />
                <DataGridTextColumn Width="*"
                                    Binding="{Binding UserName}"
                                    Header="账号" />
                <DataGridTextColumn Width="*"
                                    Binding="{Binding UserPwd}"
                                    Header="密码" />

                <DataGridTemplateColumn Width="3*"
                                        Header="操作">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <WrapPanel>
                                <Button Width="50"
                                        Margin="5,0,0,0"
                                        Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
                                        CommandParameter="{Binding UserId}"
                                        Content="编辑" />

                                <Button Width="50"
                                        Margin="5,0,0,0"
                                        Content="删除" />
                                <Button Width="70"
                                        Margin="5,0,0,0"
                                        Content="分配权限" />
                                <Button Width="50"
                                        Margin="5,0,0,0"
                                        Content="详情" />
                            </WrapPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>

            </DataGrid.Columns>

        </DataGrid>

        <StackPanel Grid.Row="2"
                    VerticalAlignment="Center">
            <TextBlock Text="分页区域" />
        </StackPanel>
    </Grid>
</UserControl>

PageAViewModel如下:

cs 复制代码
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using WPF练习17区域导航.Models;

namespace WPF练习17区域导航.ViewModels.Pages
{
    public class PageAViewModel: BindableBase, INavigationAware, IConfirmNavigationRequest
    {
        private readonly IRegionManager regionManager;
        public PageAViewModel(IRegionManager regionManager)
        {
            this.regionManager = regionManager;
        }

        private List<User> users = UserData.Users;

        public List<User> Users
        {
            get { return users; }
            set { SetProperty(ref users, value); }
        }


        public DelegateCommand<object> EditCommand
        {
            get
            {
                return new DelegateCommand<object>((obj) =>
                {
                    int userId = (int)obj;

                    // 导航到编辑页面,并且需要向编辑页面传递参数UserId
                    NavigationParameters parameters = new NavigationParameters
                    {
                        { "uid", userId }
                    };
                    // 通过NavigationParameters对象向导航传递参数
                    this.regionManager.Regions["ContentRegion"].RequestNavigate("PageAEdit", parameters);
                });
            }
        }

        #region INavigationAwre
        // 第一次进入页面不会执行,第2次之后都会进入。
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            //MessageBox.Show("PageA IsNavigationTarget!");
            return false;
        }

        // 站在当前页面的角度:OnNavigatedFrom离开当前页面
        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
            //MessageBox.Show("离开PageA!");
        }

        // 站在当前页面的角度:OnNavigatedTo到当前页面
        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            //MessageBox.Show("到PageA!");
        }
        #endregion


        #region IConfirmNavigationRequest
        // 时机:如果从PageA跳转到PageAEdit页面,需要进行导航确认,应该把导航确认的业务逻辑流写到来源页面。
        // 导航时,确认是否允许导航动作。
        // navigationContext导航上下文对象。
        // continuationCallback回调函数。
        public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            if (navigationContext.Uri.ToString() == "PageAEdit")
            {
                MessageBoxResult mbr = MessageBox.Show("是否允许进入PageAEdit页面?", "询问", MessageBoxButton.YesNo, MessageBoxImage.Question);

                bool isAllow = false;
                if (mbr == MessageBoxResult.Yes)
                {
                    isAllow = true;
                }
                continuationCallback(isAllow);
            }
            else
            {
                continuationCallback(true);
            }
        }
        #endregion
    }
}

在WPF中使用Prism框架时,INavigationAwareIConfirmNavigationRequest是两个重要的接口,它们允许视图或视图模型参与导航过程,提供了对导航行为的细粒度控制。

INavigationAware

INavigationAware接口包含三个方法,用于在导航过程中与视图或视图模型进行交互:

  • bool IsNavigationTarget(NavigationContext navigationContext):当导航到视图或视图模型时,该方法被调用,以确定当前实例是否可以处理导航请求。通常返回true,表示当前视图可以处理导航。
  • void OnNavigatedTo(NavigationContext navigationContext):当视图被导航到的时候,该方法被调用。可以用来初始化视图或处理传入的参数。
  • void OnNavigatedFrom(NavigationContext navigationContext):当视图被导航离开时,该方法被调用。可以用来保存状态或清理资源。

IConfirmNavigationRequest

IConfirmNavigationRequest接口继承自INavigationAware,并添加了一个方法**ConfirmNavigationRequest,用于在导航发生前与用户交互,以确认或取消导航:**

  • void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback):该方法提供了两个参数,一个是当前导航上下文,另一个是一个回调方法。在调用该方法时,可以显示一个确认对话框,根据用户的选择调用回调方法以决定是否继续导航。例如,如果用户选择取消,则导航将被取消。

PageAEdit.XAML如下:

XML 复制代码
<UserControl x:Class="WPF练习17区域导航.Views.Pages.PageAEdit"
             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:local="clr-namespace:WPF练习17区域导航.Views.Pages"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid HorizontalAlignment="Center"
          VerticalAlignment="Center">
        <StackPanel>
            <WrapPanel Margin="0,10,0,0">
                <TextBlock Text="账号:" />
                <TextBox Width="150"
                         Text="{Binding CurrentEditUser.UserName}" />
            </WrapPanel>
            <WrapPanel Margin="0,10,0,0">
                <TextBlock Text="密码:" />
                <TextBox Width="150"
                         Text="{Binding CurrentEditUser.UserPwd}" />
            </WrapPanel>
            <WrapPanel Margin="0,10,0,0">
                <Button Width="50"
                        Content="保存" />
            </WrapPanel>
            <WrapPanel Margin="0,10,0,0">
                <Button Width="160"
                        Command="{Binding GoOtherEditCommand}"
                        CommandParameter="2"
                        Content="详情页面跳转到其他详情" />
            </WrapPanel>
        </StackPanel>
    </Grid>
</UserControl>

PageAEditViewModel如下:

cs 复制代码
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WPF练习17区域导航.Models;

namespace WPF练习17区域导航.ViewModels.Pages
{
    // 在PageAEdit页面接收到UserId才能处理下一步的逻辑。
    // 一个跳转目标(PageAEdit页面)要想接收传递过来的参数:必须实现INavigationAware
    public class PageAEditViewModel : BindableBase, INavigationAware
    {
        private readonly IRegionManager regionManager;
        public PageAEditViewModel(IRegionManager regionManager)
        {
            this.regionManager = regionManager;
        }

        private User currentEditUser;
        public User CurrentEditUser
        {
            get { return currentEditUser; }
            set { SetProperty(ref currentEditUser, value); }
        }

        #region INavigationWare

        // IsNavigationTarget()第一次进入页面不会执行,第2次之后才会进入。

        // 通过调试,会发现IsNavigationTarget()方法返回true或false达到效果是一样。但是性能不一样。
        // 如果返回true,说明不会重新创建视图实例,不会重新创建导航上下文,即:IsNavigationTarget()方法中使用的视图实例及上下文会在OnNavigatedTo()方法中共用。
        // 如果返回false,说明会重新创建视图实例,会重新创建导航上下文,即:IsNavigationTarget()方法中使用的视图实例及上下文和OnNavigatedTo()方法中使用的视图实例及上下文不是同一个实例。

        /*IsNavigationTarget()方法用来控制视图或者视图模型实例在导航时是否被重用。当你尝试导航到一个新的视图的时候,Prism会检查当前活动的视图或者视图模型的实例,并调用他们的IsNavigationTarget方法,以决定是否可以重用这个实例。如果返回true,Prism框架将不会创建新的视图或者视图模型实例,而是重用当前的实例。这种方式非常适用于那些数据和状态不需要每次都重新加载的视图,可以提高应用程序的响应速度和资源利用率。
         */
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            Console.WriteLine(navigationContext);
            //MessageBox.Show("PageAEdit IsNavigationTarget!");
            return true;
        }

        // 站在当前页面的角度:OnNavigatedFrom离开当前页面
        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
            //MessageBox.Show("离开PageAEdit!");
        }

        // 站在当前页面的角度:OnNavigatedTo到当前页面

        // 拿参数的时机:在进入当前页面时,拿传递的参数最合理。
        // 如何拿参数?NavigationContext导航的上下文,拿到上下文就可以拿Uri, Parameters, NavigationService 操作API(导航服务)
        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            object obj = navigationContext.Parameters["uid"]; // 取传值方式1
            int uid1 = (int)obj;
            int uid2 = navigationContext.Parameters.GetValue<int>("uid");//取传值方式2

            // AsQueryable()转换成IQueryable<T>,由于IQueryable<T>继承IEnumerable<T>接口,从而可以调用LINQ扩展方法。
            CurrentEditUser = UserData.Users.AsQueryable().Where(u => u.UserId == uid1).FirstOrDefault();

            //MessageBox.Show("到PageAEdit!");
        }
        #endregion

        public DelegateCommand<object> GoOtherEditCommand
        {
            get
            {
                return new DelegateCommand<object>(obj =>
                {
                    int uid = Convert.ToInt32(obj);

                    NavigationParameters parameters = new NavigationParameters()
                    {
                        {"uid",uid}
                    };
                    this.regionManager.RequestNavigate("ContentRegion", "PageAEdit", parameters);
                });
            }
        }
    }
}

PageB如下:

XML 复制代码
<UserControl x:Class="WPF练习17区域导航.Views.Pages.PageB"
             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:local="clr-namespace:WPF练习17区域导航.Views.Pages"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <TextBlock Text="PageB" />
    </Grid>
</UserControl>

PageBViewModel如下:

cs 复制代码
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 区域导航.ViewModels.Pages
{
    public class PageBViewModel : BindableBase
    {
    }
}

PageC如下:

XML 复制代码
<UserControl x:Class="WPF练习17区域导航.Views.Pages.PageC"
             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:local="clr-namespace:WPF练习17区域导航.Views.Pages"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <TextBlock Text="PageC" />
    </Grid>
</UserControl>

PageCViewModel如下:

cs 复制代码
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WPF练习17区域导航.ViewModels.Pages
{
    public class PageCViewModel : BindableBase
    {

    }
}

4.最后写好MainWindowViewModel代码

cs 复制代码
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using System.Windows.Controls;

namespace WPF练习17区域导航.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        // 导航日志服务,主要负责提供导航日志的管理,如:添加导航日志,根据导航日志可以前进,后退等等。
        // 添加导航日志的时机,应该在导航跳转成功后,添加导航日志。
        private IRegionNavigationJournal journal;

        private readonly IRegionManager regionManager;
        // 使用依赖注入的把区域管理服务注入当前的VM。
        public MainWindowViewModel(IRegionManager regionManager, IRegionNavigationJournal journal)
        {
            this.regionManager = regionManager;
            this.journal = journal;
        }

        private string _title = "Prism Application";

        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        public DelegateCommand<object> TogglePage
        {
            get
            {
                return new DelegateCommand<object>(obj =>
                {
                    Button button = obj as Button;

                    // 导航跳转
                    // 前提:需要把视图先扔到Ioc容器
                    // 1。拿区域管理器(其实是区域给VM提供的一种服务而异)
                    // 2。从区域管理器中拿某个区域
                    // 3。调用区域的API(导航服务)

                    // 参数1:导航的目标视图名称。参数2:导航后(有可能成功,也有可能失败)的回调函数。参数3:导航参数。
                    this.regionManager.Regions["ContentRegion"].RequestNavigate(
                        button.Content.ToString(),
                        NavigationCallback,
                        new NavigationParameters()
                    );
                });
            }
        }

        private void NavigationCallback(NavigationResult navigationResult)
        {
            // 导航成功
            //if(navigationResult.Result==true)
            //if((bool)navigationResult.Result)
            if (navigationResult.Result.Value == true)
            {
                // 添加导航日志的时机,应该在导航跳转成功后,添加导航日志。
                // navigationResult.Context先拿导航上下文。
                // NavigationService导航服务
                // Journal日志
                journal = navigationResult.Context.NavigationService.Journal;
            }
        }


        public DelegateCommand ForwardCommand
        {
            get
            {
                return new DelegateCommand(() =>
                {
                    if (journal != null && journal.CanGoForward)
                    {
                        journal.GoForward();
                    }
                });
            }
        }

        public DelegateCommand BackCommand
        {
            get
            {
                return new DelegateCommand(() =>
                {
                    if (journal != null && journal.CanGoBack)
                    {
                        journal.GoBack();
                    }
                });
            }
        }

    }
}

5.效果如下:

相关推荐
林子漾5 小时前
【paper】分布式无人水下航行器围捕智能目标
分布式·wpf
wyh要好好学习12 小时前
C# WPF 记录DataGrid的表头顺序,下次打开界面时应用到表格中
开发语言·c#·wpf
lgcgkCQ1 天前
任务调度中心-XXL-JOB使用详解
java·wpf·定时任务·任务调度
Vicky&James1 天前
英雄联盟客户端项目:从跨平台Uno Platform到Win UI3的转换只需要30分钟
github·wpf·跨平台·英雄联盟·winui·unoplatform
她说彩礼65万1 天前
WPF程序设置单例启动(互斥体)
wpf
就是有点傻1 天前
WPF中Prism框架中 IContainerExtension 和 IRegionManager的作用
wpf
月落.1 天前
WPF中MVVM工具包 CommunityToolkit.Mvvm
wpf·mvvm
月落.1 天前
WPF Prism框架
wpf·prism
Crazy Struggle2 天前
.NET 8.0 通用管理平台,支持模块化、WinForms 和 WPF
vue·wpf·winform·.net 8.0·通用权限管理