【问题标题】:WPF Navigation while Keeping Menubar (Header) and Footer FixedWPF 导航同时保持菜单栏(页眉)和页脚固定
【发布时间】:2021-09-12 23:04:20
【问题描述】:

我们过去使用 WinForms 开发应用程序,现在我们正尝试将其迁移到 WPF,从零开始。在我们的应用程序中,屏幕上有 3 个主要部分,即页眉(所有主菜单项)、正文(基于 MDI 容器,内容可以更改)和页脚(显示一般状态、徽标等)。在与标题部分不同的菜单项上,正文部分会将其子项更改为该面板/表单。

互联网上有很多很好的示例/教程,但我对如何实现创建允许切换身体部位视图的导航服务感到困惑。

如有任何建议,将不胜感激,在此先感谢。

【问题讨论】:

    标签: c# wpf mvvm navigationservice


    【解决方案1】:

    确实有多种方法可以存档此结果。我将尝试解释获得结果的非常基本/最简单的方法。 虽然这不会提供与菜单控件结合的示例,但我认为它会帮助您理解这个概念

    在您的 MainWindow 中,您可以根据需要拆分使用网格布局并将空间分成 3 个部分。您的主窗口 Xaml 应如下所示:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <ContentControl x:Name="Header"/>
        <ContentControl x:Name="Content" Grid.Row="1/>
        <ContentControl x:Name="Footer" Grid.Row="2"/>
    </Grid>
    

    在您的内容控件中,您可以为页眉、内容、页脚插入“用户控件”。现在到导航部分: 如前所述,有很多方法可以存档,我将描述我认为最简单的方法(但不是最灵活的方法,所以请记住这一点)。

    首先我建议制作一个导航模型:

    public class NavigationModel
    {
        public NavigationModel(string title, string description, Brush color)
        {
            Title = title;
            Description = description;
            Color = color;
        }
    
        public string Title { get; set; }
        
        public string Description { get; set; }
        
        public Brush Color { get; set; }
    
        public override bool Equals(object obj)
        {
            return obj is NavigationModel model &&
                   Title == model.Title &&
                   Description == model.Description &&
                   Color == model.Color;
        }
    
        public override int GetHashCode()
        {
            return HashCode.Combine(Title, Description, Color);
        }
    }
    

    我们创建一个新类来处理导航集合,我们称之为导航服务。

        public class NavigationService
    {
    
        public List<NavigationModel> NavigationOptions { get=>NavigationNameToUserControl.Keys.ToList(); }
    
        public UserControl NavigateToModel(NavigationModel _navigationModel)
        {
            if (_navigationModel is null) 
                //Or throw exception
                return null;
            if (NavigationNameToUserControl.ContainsKey(_navigationModel))
            {
                return NavigationNameToUserControl[_navigationModel].Invoke();
            }
            //Ideally you should throw here Custom Exception
            return null;
        }
    
        //Usage of the Func, provides each call new initialization of the view
        //If you need initialized views, just remove the Func
        //-------------------------------------------------------------------
        //Readonly is used only for performance reasons
        //Of course there is option to add the elements to the collection, if dynamic navigation mutation is needed
        private readonly Dictionary<NavigationModel, Func<UserControl>> NavigationNameToUserControl = new Dictionary<NavigationModel, Func<UserControl>>
        {
            { new NavigationModel("Navigate To A","This will navigate to the A View",Brushes.Aqua), ()=>{ return new View.ViewA(); } },
            { new NavigationModel("Navigate To B","This will navigate to the B View",Brushes.GreenYellow), ()=>{ return new View.ViewB(); } }
        };
    
        #region SingletonThreadSafe
        private static readonly object Instancelock = new object();
        
        private static NavigationService instance = null;
    
        public static NavigationService GetInstance
        {
            get
            {
                if (instance == null)
                {
                    lock (Instancelock)
                    {
                        if (instance == null)
                        {
                            instance = new NavigationService();
                        }
                    }
                }
                return instance;
            }
        }
        #endregion
    }
    

    此服务将为我们提供接收所需 UserControll 的操作(请注意,我使用的是 UserControl 而不是页面,因为它们提供了更大的灵活性)。
    不是我们创建额外的转换器,我们将绑定到 xaml:

       public class NavigationConverter : MarkupExtension, IValueConverter
    {
        private static NavigationConverter _converter = null;
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (_converter is null)
            {
                _converter = new NavigationConverter();
            }
            return _converter;
        }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            NavigationModel navigateTo = (NavigationModel)value;
            NavigationService navigation = NavigationService.GetInstance; 
                if (navigateTo is null) 
                return null;
            return navigation.NavigateToModel(navigateTo);
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            => null;
    }
    

    在我们的 MainWindows.xaml 中,通过 xmlns 添加对 Converter 命名空间的引用,例如:

    xmlns:Converter="clr-namespace:SimpleNavigation.Converter"
    

    并创建转换器实例:

    <Window.Resources>
        <Converter:NavigationConverter x:Key="NavigationConverter"/>
    </Window.Resources>
    

    请注意,您的项目名称将具有不同的命名空间 并将 Add datacontext 设置为我们的 Navigation Service 的实例: 如果您使用的是 MVVM,您可以通过 MainWindow.Xaml.CS 或创建一个 ViewModel

    MainWindow.Xaml.CS:

     public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = Service.NavigationService.GetInstance.NavigationOptions;
        }
    }
    

    现在剩下要做的就是导航。我不知道您的 UX 怎么样,所以我将仅提供 MainWindow.xaml 的 github 中的示例。希望你能做到最好:

    <Grid>
    <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <StackPanel>
            <ListView 
                x:Name="NavigationList"
                ItemsSource="{Binding}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Border 
                        Height="35"
                        BorderBrush="Gray"
                        Background="{Binding Color}"
                        ToolTip="{Binding Description}"
                        BorderThickness="2">
                        <TextBlock 
                            VerticalAlignment="Center"
                            FontWeight="DemiBold"
                            Margin="10"
                            Text="{Binding Title}" />
                    </Border>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        </StackPanel>
        <ContentControl
            Grid.Column="1"
            Content="{Binding ElementName=NavigationList,Path=SelectedItem,Converter={StaticResource NavigationConverter}}"/>
    </Grid>
    

    以防万一我会给你一个 github 的链接,这样对你来说会更容易 https://github.com/6demon89/Tutorials/blob/master/SimpleNavigation/MainWindow.xaml

    使用相同的原理来使用菜单导航

      <Window.DataContext>
        <VM:MainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <Converter:NavigationConverter x:Key="NavigationConverter"/>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="Navigaiton" 
                    ItemsSource="{Binding NavigationOptions}">
                    <MenuItem.ItemTemplate>
                        <DataTemplate>
                            <MenuItem 
                                Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=Window}}"
                                CommandParameter="{Binding}"
                                Header="{Binding Title}"
                                Background="{Binding Color}"
                                ToolTip="{Binding Description}">
                            </MenuItem>
                        </DataTemplate>
                    </MenuItem.ItemTemplate>
            </MenuItem>
        </Menu>
        <ContentControl 
            Grid.Row="1"
            Background="Red"
            BorderBrush="Gray"
            BorderThickness="2"
            Content="{Binding CurrentView,Converter={StaticResource NavigationConverter}}"/>
        <Border Grid.Row="2" Background="{Binding CurrentView.Color}">
            <TextBlock Text="{Binding CurrentView.Description}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
    </Grid>
    

    我们在虚拟机列表中有导航模型、当前模型和导航命令:

      public class MainViewModel:INotifyPropertyChanged
    {
        public List<NavigationModel> NavigationOptions { get => NavigationService.GetInstance.NavigationOptions; }
    
        private NavigationModel currentView;
    
        public NavigationModel CurrentView
        {
            get { return currentView; }
            set 
            { 
                currentView = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CurrentView"));
            }
        }
    
        RelayCommand _saveCommand;
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public ICommand NavigateCommand
        {
            get
            {
                if (_saveCommand == null)
                {
                    _saveCommand = new RelayCommand(Navigate);
                }
                return _saveCommand;
            }
        }
    
        private void Navigate(object param)
        {
            if(param is NavigationModel nav)
            {
                CurrentView = nav;
            }
        }
    
    }
    

    抱歉回复太长了

    【讨论】:

    • 嗨,恶魔,这是一个很好的例子和解释。我已经下载了您的示例代码(顺便说一句,与 BLE 的合作很好,我实际上也在与子系统进行通信)对其进行了一些简单的更改并且它可以工作。谢谢你的帮助:)
    • 很高兴它有帮助。如果这回答了您的问题,请标记答案;)
    • 另一方面,如前所述,对于 wpf 中的中小型工具/应用程序,这是一个不错的简单解决方案。但是对于更大的应用程序,我会推荐更模块化的方法!请查看 prism 等第三方库,它可以使您的应用程序非常灵活。
    【解决方案2】:

    我认为您不必从头开始。你可以看看:

    https://qube7.com/guides/navigation.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-08-02
      • 2010-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-05
      • 1970-01-01
      • 2023-03-16
      相关资源
      最近更新 更多