如果有人可能觉得标题令人困惑,我想道歉,我在这里描述的问题比我想象的要复杂,我很难在标题中总结它。
基本上我有一个带有主屏幕的应用程序,它由一个主空屏幕组成,用户可以在其上显示其他视图和侧面的侧导航面板。但是,当我打开主屏幕部分上显示的不同视图时,它也恰好覆盖了导航面板的扩展部分。发生这种情况的原因非常明显。
为了解决这个问题,我决定将导航面板和主屏幕分成两个单独的视图,以便更容易地操作视图的层次结构,以便导航面板始终位于顶部,位于所有其他显示的视图之上。令我失望的是,这样的选项似乎不存在,所以我想出了另一个想法 - 在按下“打开菜单”按钮时切换活动视图,并在再次按下时将其更改回当前视图。
这就是带有侧面导航面板的视图现在的样子:
<UserControl x:Class="ApkaJezykowa.MVVM.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ApkaJezykowa"
xmlns:viewModel="clr-namespace:ApkaJezykowa.MVVM.ViewModel"
xmlns:view="clr-namespace:ApkaJezykowa.MVVM.View"
mc:Ignorable="d"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="Transparent">
<UserControl.DataContext>
<viewModel:MainViewModel/>
</UserControl.DataContext>
<Border Background="Transparent">
<Grid>
<Grid>
<Grid x:Name="nav_pnl" Width="80" Background=" #484848" HorizontalAlignment="Left" Grid.ZIndex="4">
<StackPanel x:Name="st_pln">
<Grid Height="80">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="85,0,0,0"
Text="Menu"
FontSize="22"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{StaticResource Font_Style}">
<TextBlock.Effect>
<DropShadowEffect BlurRadius="10"
ShadowDepth="1"
Direction="-90"
Color="White"/>
</TextBlock.Effect>
</TextBlock>
<ToggleButton x:Name="tg_btn"
Height="60"
Width="60"
BorderThickness="0"
HorizontalAlignment="Left"
Margin="10,0,0,0"
Style="{StaticResource tb_style}" Unchecked="tg_btn_Unchecked" Checked="tg_btn_Checked">
<ToggleButton.Background>
<ImageBrush ImageSource="pack://application:,,,/ApkaJezykowa;component/Images/menu.png" Stretch="Uniform"/>
</ToggleButton.Background>
<ToggleButton.Triggers>
<EventTrigger RoutedEvent="ToggleButton.Unchecked">
<BeginStoryboard>
<Storyboard x:Name="HideStackPanel">
<DoubleAnimation
Storyboard.TargetName="nav_pnl"
Storyboard.TargetProperty="Width"
BeginTime="0:0:0"
From="260" To="80"
Duration="0:0:0.1"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="ToggleButton.Checked">
<BeginStoryboard>
<Storyboard x:Name="ShowStackPanel">
<DoubleAnimation
Storyboard.TargetName="nav_pnl"
Storyboard.TargetProperty="Width"
BeginTime="0:0:0"
From="80" To="260"
Duration="0:0:0.1"
></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToggleButton.Triggers>
</ToggleButton>
</Grid>
<ListView x:Name="LV" Background="#484848" ScrollViewer.HorizontalScrollBarVisibility="Disabled" BorderThickness="0" Canvas.ZIndex="3">
<--!just a list of options-->
</ListView>
</StackPanel>
</Grid>
</Grid>
<Grid x:Name="img_bg" Margin="80,0,0,0" PreviewMouseLeftButtonDown="BG_PreviewMouseLeftButtonDown">
<view:MainScreenView Panel.ZIndex="1"/>
<ContentControl Content="{Binding SelectedViewModel}" Panel.ZIndex="1"/>
</Grid>
<ContentControl Content="{Binding MainView}"/>
</Grid>
</Border>
</UserControl>
但是后来我遇到了当前设计的缺陷。每个视图的 ViewModel 都有自己的 UpdateViewCommand,它有自己的触发器,决定接下来应该显示哪个视图。
导航面板的示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using ApkaJezykowa.MVVM.ViewModel;
namespace ApkaJezykowa.Commands
{
internal class UpdateViewCommand : ICommand
{
private MainViewModel viewModel;
public UpdateViewCommand(MainViewModel viewModel)
{
this.viewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if(parameter.ToString() == "Lessons")
{
viewModel.SelectedViewModel = new LessonsViewModel();
}
if (parameter.ToString() == "Dictionary")
{
viewModel.SelectedViewModel = new DictionaryViewModel();
}
if (parameter.ToString() == "Info")
{
viewModel.SelectedViewModel = new InfoViewModel();
}
if (parameter.ToString() == "Settings")
{
viewModel.SelectedViewModel = new SettingsViewModel();
}
if(parameter.ToString() == "Editor")
{
viewModel.SelectedViewModel = new LessonEditorViewModel();
}
}
}
}
...我有大约两打。更改以这种方式设计的视图本质上阻止了我设置此解决方案,因为导航面板本质上需要知道哪个 ViewModel 当前处于活动状态。
所以,目前我看到解决这个混乱局面的两到三种方法:
我希望我的问题的解释足够清楚以便于理解。一如既往,提前感谢您帮助我解决这个问题的任何尝试,无论成功与否。
我强烈怀疑您将 ViewModel 与从代码隐藏中提取的 UI 代码混淆了。 WPF(适用于所有 XAML 平台)中 ViewModel 的功能是在其属性中反映 Model。 ViewModel 不应该跟踪视图、以某种方式切换它们、以任何其他方式管理它们。
理论上,您应该让某些 Model 根据 ViewModel 请求更改其状态。这些模型状态应该反映在 ViewModel 属性(或多个属性)中。跟踪这些属性的视图将自动更新,包括替换窗口区域(或多个区域)中的视图。
具体到你的实现,如果你不考虑我上面描述的概念错误,那么你需要使用标准实现命令。例如,您可以从这里获取:我的基类实现示例:BaseInpc、RelayCommand、RelayCommandAsync、RelayCommand
并在View级别实现一个单独的“Navigator”类,该类将在App中初始化。
using Simplified;
using System.Windows.Markup;
namespace Core2024.SO
{
[ContentProperty(nameof(ViewModelTypes))]
public class SimpleNavigator : BaseInpc
{
public Dictionary<string, Type> ViewModelTypes { get; } = [];
private object? _currentViewModel;
private RelayCommand<string>? _navigateCommand;
public object? CurrentViewModel { get => _currentViewModel; private set => Set(ref _currentViewModel, value); }
public RelayCommand NavigateCommand => _navigateCommand
??= new RelayCommand<string>
(
name =>
{
Type viewModelType = ViewModelTypes[name];
var ctor = viewModelType.GetConstructor([]);
CurrentViewModel = ctor!.Invoke([]);
},
ViewModelTypes.ContainsKey
);
}
}
<Application --------------------
--------------------
Startup="OnStartup">
<Application.Resources>
<so:SimpleNavigator x:Key="navigator"/>
private void OnStartup(object sender, StartupEventArgs e)
{
SimpleNavigator navigator =(SimpleNavigator) FindResource(nameof(navigator));
navigator.ViewModelTypes["Lessons"] = typeof(LessonsViewModel);
navigator.ViewModelTypes["Dictionary"] = typeof(DictionaryViewModel);
navigator.ViewModelTypes["Info"] = typeof(InfoViewModel);
}
用途:
<ContentControl Content="{Binding CurrentViewModel,
Source{StaticResource navigator}}"
Panel.ZIndex="1"/>
在任何级别视图中:
<Button Command="{Binding NavigateCommand,
Source{StaticResource navigator}}"
CommandParameter="ViewModel key"/>