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.效果如下:

相关推荐
晚安苏州6 小时前
WPF DataTemplate 数据模板
wpf
甜甜不吃芥末1 天前
WPF依赖属性详解
wpf
Hat_man_1 天前
WPF制作图片闪烁的自定义控件
wpf
晚安苏州3 天前
WPF Binding 绑定
wpf·wpf binding·wpf 绑定
wangnaisheng3 天前
【WPF】RenderTargetBitmap的使用
wpf
dotent·3 天前
WPF 完美解决改变指示灯的颜色
wpf
orangapple5 天前
WPF 用Vlc.DotNet.Wpf实现视频播放、停止、暂停功能
wpf·音视频
ysdysyn5 天前
wpf mvvm 数据绑定数据(按钮文字表头都可以),根据长度进行换行,并把换行的文字居中
c#·wpf·mvvm
orangapple5 天前
WPF 使用LibVLCSharp.WPF实现视频播放、停止、暂停功能
wpf
晚安苏州5 天前
WPF ControlTemplate 控件模板
wpf