禹城市住房和城乡建设局网站,文网文许可证,梁山有没有做企业网站的,高校网络网站建设意义及措施WPF中在MVVM模式下实现导航功能
一、利用TabControl
使用场景#xff1a;项目小#xff0c;不用考虑内存开销的问题。
实现方式1-手动指定ViewModel
分别定义3个UserControl作为View用于演示 UserControl...GridStackPanel OrientationVertic…WPF中在MVVM模式下实现导航功能
一、利用TabControl
使用场景项目小不用考虑内存开销的问题。
实现方式1-手动指定ViewModel
分别定义3个UserControl作为View用于演示 UserControl...GridStackPanel OrientationVerticalTextBlockHorizontalAlignmentCenterVerticalAlignmentTopTextPage 1 /TextBlockd:TextPage 1FontSize50Text{Binding PageMessage} //StackPanel/Grid/UserControl分别定义ViewModel public abstract class PageViewModelBase {public string? Header { get; set; }}public class MainViewModel {public ListPageViewModelBase ViewModels { get; }public MainViewModel(Page1ViewModel p1, Page2ViewModel p2, Page3ViewModel p3){ViewModels new ListPageViewModelBase { p1, p2, p3 };}}public class Page1ViewModel : PageViewModelBase{public Page1ViewModel() Header Page 1;public string PageMessage { get; set; } Hello, Page 1;}public class Page2ViewModel : PageViewModelBase{public Page2ViewModel() Header Page 2;public string PageMessage { get; set; } Hello, Page 2;}public class Page3ViewModel : PageViewModelBase{public Page3ViewModel() Header Page 3;public string PageMessage { get; set; } Hello, Page 3;}在MainWindow上定义Tabcontrol Window...GridTabControl ItemsSource{Binding ViewModels}TabItem HeaderPag1view:Page1view:Page1.DataContextlocal:Page1ViewModel //view:Page1.DataContext/view:Page1/TabItemTabItem HeaderPag2view:Page1view:Page1.DataContextlocal:Page2ViewModel //view:Page1.DataContext/view:Page1/TabItemTabItem HeaderPag3view:Page1view:Page1.DataContextlocal:Page3ViewModel //view:Page1.DataContext/view:Page1/TabItem/TabControl/Grid/Window这种方式需要手动指定每个View的ViewModel
实现方式2-利用ItemTemplate
在MainViewModel中声明一个ViewModel列表
public class MainViewModel
{public ListPageViewModelBase ViewModels { get; }public MainViewModel(Page1ViewModel p1, Page2ViewModel p2, Page3ViewModel p3){ViewModels new ListPageViewModelBase { p1, p2, p3 };}
}在MainWindow中为TabControl指定ItemTemplate上一步声明的ViewModel列表作为 TabControl 的 ItemsSource为 TabControl.Resources 添 加多个 DataTemplate指定 VM 对应什么样的 Page
Window d:DataContext{d:DesignInstance Typelocal:MainViewModel}....GridTabControl ItemsSource{Binding ViewModels}TabControl.ItemTemplateDataTemplateTextBlock Text{Binding Header}//DataTemplate/TabControl.ItemTemplateTabControl.ResourcesDataTemplate DataType{x:Type local:Page1ViewModel}view:Page1//DataTemplateDataTemplate DataType{x:Type local:Page2ViewModel}view:Page2//DataTemplateDataTemplate DataType{x:Type local:Page3ViewModel}view:Page3//DataTemplate/TabControl.Resources /TabControl/Grid
/Window这样的好处是自动会为不同的View绑定了相应的ViewModel。
小技巧在xaml中加上了d:DataContext{d:DesignInstance Typelocal:MainViewModel}这样在写Binding的时候就有了智能提示。
以上两种方式均可结合依赖注入的方式来实现
二、自定义NavigationService服务 实现一个NavigationService服务并作为单例 class NavigationService{//设置一个单例服务public static NavigationService Instance { get; private set; } new NavigationService();//声明一个事件当更改CurrentViewModel时触发public event Action? CurrentViewModelChanged;//设置一个当前VM的属性并在属性改变时触发CurrentViewModelChangedprivate ViewModelBase? currentViewModel;public ViewModelBase? CurrentViewModel{get currentViewModel;set{currentViewModel value;CurrentViewModelChanged?.Invoke();}}//页面导航方法给CurrentViewModel赋值触发CurrentViewModelChanged事件public void NavigateTo(ViewModelBase viewModel)CurrentViewModel viewModel;}设置MainViewModel中的CurrentViewModel属性 public class ViewModelBase : ObservableObject{}public partial class MainViewModel : ViewModelBase{[ObservableProperty]private ViewModelBase? currentViewModel;//当前的VMpublic MainViewModel(){//为事件绑定委托方法设置CurrentVM和NavigationService中的CurrentVM保持一致NavigationService.Instance.CurrentViewModelChanged () {CurrentViewModel NavigationService.Instance.CurrentViewModel;};//调用导航方法NavigationService.Instance.NavigateTo(new LoginViewModel());}}其他两个ViewModel分别为 public partial class LoginViewModel : ViewModelBase{[ObservableProperty]string? userName Sean;[RelayCommand]void Login(){NavigationService.Instance.NavigateTo(new HomeViewModel());}}public partial class HomeViewModel : ViewModelBase{[ObservableProperty]string? userName;[RelayCommand]void Logout(){NavigationService.Instance.NavigateTo(new LoginViewModel());}}使用ContentControl作为MainWindow上不同页面载体显示内容并借助DataTemplate来实现View和ViewModel的映射 Window ...ContentControl Content{Binding CurrentViewModel}ContentControl.ResourcesDataTemplate DataType{x:Type vm:LoginViewModel}view:Login //DataTemplateDataTemplate DataType{x:Type vm:HomeViewModel}view:Home //DataTemplate/ContentControl.Resources/ContentControl/Window在ContentControl.Resources中设置DataTemplate根据DataType自动选择相应的VM这样做的好处是会自动将View和VM进行了绑定。
改进
单例方式可以采用依赖注入的方式来实现在NavigationService服务中可以改进页面导航的方法
public void NavigateToT() where T : ViewModelBase CurrentViewModel App.Current.Services.GetServiceT();//在调用导航方法时可以使用
navigationService.NavigateToHomeViewModel();三、借助ValueConverter
实现上一章节的功能这种方法本质上是通过View来自动绑定VM。
定义Page的枚举 public enum ApplicationPage{Empty,Login,Home}定义各ViewModel public class ViewModelBase : ObservableObject{}public partial class MainViewModel : ViewModelBase{//MainViewModel中的CurrentPage是一个枚举类型[ObservableProperty]ApplicationPage currentPage;public MainViewModel(){CurrentPage ApplicationPage.Login;}}public partial class LoginViewModel : ViewModelBase{public string UserName { get; set; } AngelSix;[RelayCommand]void Login(){var mainVM App.Current.MainWindow.DataContext as MainViewModel;mainVM!.CurrentPage ApplicationPage.Home;}}public partial class HomeViewModel : ViewModelBase{[RelayCommand]void Logout(){var mainVM App.Current.MainWindow.DataContext as MainViewModel;mainVM!.CurrentPage ApplicationPage.Login;}}定义Page基类和各个Page 这种方法本质上是通过View来自动绑定VM所以在此处使用泛型 public abstract class BasePageVM : UserControl where VM : ViewModelBase, new(){public BasePage(){DataContext new VM();}}实现Home页面
将Home.xaml.cs中的继承删掉以为它和Home.xaml相互为分部类只在一个分部类上实现继承就可以。 local:BasePage x:TypeArgumentsvm:HomeViewModel...!--x:TypeArguments指定泛型--GridTextBlock HorizontalAlignmentCenterVerticalAlignmentCenterTextHomeFontSize32 /Button Margin10 Grid.Row1HorizontalAlignmentRightVerticalAlignmentBottomContentLogoutCommand{Binding LogoutCommand} //Grid/local:BasePage实现Login页面
方法和实现Home页面方法相同 local:BasePagex:TypeArgumentsvm:LoginViewModel ...GridBorderPadding10HorizontalAlignmentCenterVerticalAlignmentCenterBorderBrushLightGrayBorderThickness1CornerRadius10StackPanel Width300TextBlock HorizontalAlignmentCenter FontSize28Login/TextBlockSeparator Margin0,10 /TextBlockUser name:/TextBlockTextBoxMargin0,10InputMethod.IsInputMethodEnabledFalseText{Binding UserName} /TextBlockPassword:/TextBlockPasswordBox Margin0,10 Password123456 /Button Command{Binding LoginCommand} ContentLogin //StackPanel/Border/Grid/local:BasePage定义PageViewConverter public class PageViewConverter : IValueConverter
{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){switch ((ApplicationPage)value){case ApplicationPage.Empty:return new TextBlock { Text 404 Not Found };case ApplicationPage.Login:return new Login();case ApplicationPage.Home:return new Home();default:throw new ArgumentException(Invalid value passed to ApplicationPageViewConverter);}}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}
}完成MainWindow Window ...Window.DataContextlocal:MainViewModel//Window.DataContextWindow.Resourcesshare:PageViewConverter x:KeypageConv//Window.ResourcesContentControl Content{Binding CurrentPage,Converter{StaticResource pageConv}}//Window改进 可以结合依赖注入的方式来实现 导航方法可以封装为一个NavigationService服务 //封装服务class NavigationService{public static NavigationService Instance { get; } new NavigationService();private MainViewModel mainVM;public void Navigate(ApplicationPage page){if (mainVM null){mainVM (MainViewModel)App.Current.MainWindow.DataContext;}mainVM.CurrentPage page;}}//原来的方式void Logout(){var mainVM App.Current.MainWindow.DataContext as MainViewModel;mainVM!.CurrentPage ApplicationPage.Login;}//使用封装好的服务void Login(){NavigationService.Instance.Navigate(ApplicationPage.Login);}四、使用Frame和NavigationService
实现上一章节功能本质上是使用依赖注入的方式将View和ViewModel进行绑定并利用Frame的自带的Navigate方法进行导航
定义ViewModel public class ViewModelBase : ObservableObject{}public partial class MainWindowViewModel : ViewModelBase{private readonly NavigationService navigationService;//依赖注入public MainWindowViewModel(NavigationService navigationService){this.navigationService navigationService;}[RelayCommand]void Loaded(){ //navigationService实现的导航方法navigationService.NavigateLoginViewModel();}}public partial class HomeViewModel : ViewModelBase{[ObservableProperty]string? userName;}public partial class LoginViewModel : ViewModelBase{private readonly NavigationService navigationService;//依赖注入public string UserName { get; set; } Sergio;public LoginViewModel(NavigationService navigationService){this.navigationService navigationService;}[RelayCommand]void Login(){ //navigationService实现的导航方法此处进行了传参navigationService.NavigateHomeViewModel(new Dictionarystring, object?{[nameof(HomeViewModel.UserName)] UserName});}}定义View
主窗口使用Behaviors实现mvvm模式
Windowxmlns:bhttp://schemas.microsoft.com/xaml/behaviorsb:Interaction.Triggersb:EventTriggerb:InvokeCommandAction Command{Binding LoadedCommand} //b:EventTrigger/b:Interaction.Triggers
/Window主窗口后台类
public partial class MainWindow : Window
{public MainWindow(MainWindowViewModel viewModel,Frame frame){InitializeComponent();DataContext viewModel;AddChild(frame);}
}其他View
!--使用Page来承载内容--
Page ...GridTextBlock HorizontalAlignmentCenterVerticalAlignmentCenterd:TextHello, world!Text{Binding UserName, StringFormatHello, {0}!}FontSize32 //Grid
/PagePage ...GridBorder Padding10HorizontalAlignmentCenterVerticalAlignmentCenterBorderThickness1CornerRadius10BorderBrushLightGrayStackPanel Width300TextBlock HorizontalAlignmentCenter FontSize28Login/TextBlockSeparator Margin0,10 /TextBlockUser name:/TextBlockTextBox Margin0,10 Text{Binding UserName} InputMethod.IsInputMethodEnabledFalse /TextBlockPassword:/TextBlockPasswordBox Margin0,10 Password123456 /Button ContentLogin Command{Binding LoginCommand} //StackPanel/Border/Grid
/Page
在后台类中使用依赖注入的方式定义DataContext
public Home(HomeViewModel viewModel)
{InitializeComponent();DataContext viewModel;
}public Login(LoginViewModel viewModel)
{InitializeComponent();DataContext viewModel;
}实现NavigationService public class NavigationService{//注册了单例的Frameprivate readonly Frame? mainFrame;public NavigationService(Frame? frame){mainFrame frame;//要使用LoadCompleted事件mainFrame.LoadCompleted MainFrame_LoadCompleted;}private void MainFrame_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e){if (e.ExtraData is not Dictionarystring,object? extraData){return;}if ((mainFrame?.Content as FrameworkElement)?.DataContext is not ViewModelBase vm){return;}foreach (var item in extraData){//为每个属性赋值vm.GetType().GetProperty(item.Key)?.SetValue(vm, item.Value);}}//根据VM类型查找View要注意VM和View的命名规范private Type? FindViewT(){return Assembly.GetAssembly(typeof(T))?.GetTypes().FirstOrDefault(x x.Name typeof(T).Name.Replace(ViewModel, ));}public void NavigateT(Dictionarystring,object?? extraDatanull) where T:ViewModelBase{var viewType FindViewT();if (viewType is null)return;var page App.Current.Services.GetService(viewType) as Page;//利用Frame的Navigate方法进行导航和传参mainFrame?.Navigate(page,extraData);} }注册需要的类此案例在App.cs中进行注册 public partial class App : Application{public IServiceProvider Services { get; }public static new App Current (App)Application.Current;public App(){var container new ServiceCollection();container.AddSingleton(_ new Frame { NavigationUIVisibility NavigationUIVisibility.Hidden });container.AddSingletonMainWindow();container.AddSingletonMainWindowViewModel();container.AddTransientLogin();container.AddTransientHome();container.AddTransientLoginViewModel();container.AddTransientHomeViewModel();container.AddSingletonNavigationService();Services container.BuildServiceProvider();}protected override void OnStartup(StartupEventArgs e){base.OnStartup(e);MainWindow Services.GetRequiredServiceMainWindow();MainWindow.Show();}}