如何通过主视图模型将 UserControl 内容更改为另一个 UserControl。如何在内容之间导航
How to change UserControl content to another UserControl through main view model. How to navigate between content
我有一个主 window,带有用于导航的侧边栏和一个用户控件,我在其中显示 3 个视图(默认视图 1、视图 2)。在主视图模型(称为 AppVM)中,我将 contentcontrol 初始化为默认视图,该视图有一个按钮可以进入 view1(除了导航侧边栏)。我在 AppVM 中有命令可以切换到三个视图中的任何一个。 View1 然后有另一个按钮应该移动到 view2(使用主视图模型中存在的命令)。但是,每当我按下 view1 中的按钮(移动到 view2)时,显示都不会改变。奇怪的是,在调试时,当按下 view1 中的按钮时,内容控件绑定到的变量被设置为默认视图,而不是当前视图即 view1。
我认为我设置命令的方式会创建内容控件绑定变量的新实例,但我不知道如何让它使用相同的实例而不是一次又一次地打开新实例。
主视图模型 (AppVM)
public class AppVM : ObservableObject
{
//Create a property that controls current view
private object _currentView;
public object CurrentView
{
get { return _currentView; }
private set
{
OnPropertyChanged(ref _currentView, value);
}
}
private string _textboxText;
public string TextboxText
{
get { return _textboxText; }
set
{
OnPropertyChanged(ref _textboxText, value);
}
}
//Instantiate the relaycommands, we will need to instantiate relaycommand objects for every command we need to perform.
//This means that we will need to do this for preses of all buttons
public RelayCommand View1ButtonCommand { get; private set; }
public RelayCommand View2ButtonCommand { get; private set; }
public RelayCommand DefaultCommand { get; private set; }
public AppVM()
{
//CurrentView = this;
CurrentView = new DefaultVM();
View1ButtonCommand = new RelayCommand(ShowView1, AlwaysTrueCommand);
View2ButtonCommand = new RelayCommand(ShowView2, AlwaysTrueCommand);
DefaultCommand = new RelayCommand(ShowDefault, AlwaysTrueCommand);
}
public void ShowDefault(object dummy)
{
// CurrentView = null;
CurrentView = new DefaultVM();
}
public void ShowView1(object dummy)
{
//CurrentView = null;
CurrentView = new View1(dummy as string);
}
public void ShowView2(object dummy)
{
// CurrentView = null;
CurrentView = new View2();
}
public bool AlwaysTrueCommand(object dummy)
{
return true;
}
}
View1 虚拟机
public class View1VM : ObservableObject {
public InfoClass View1InfoClass { get; set; }
public View1VM() { View1InfoClass = new InfoClass //Apparently I need to instantiate and initialize this to activate binding {
FirstName = "Abbas",
//FirstName = passedInforClass,
LastName = "Syed",
Number = 12
};
} }
view1.xaml中的命令
<UserControl.Resources>
<vm:AppVM x:Name="AppVMinView1" x:Key="AppVMinView1"></vm:AppVM>
</UserControl.Resources>
<UserControl.DataContext>
<vm:View1VM></vm:View1VM>
</UserControl.DataContext>
<Grid Background="Aqua">
<StackPanel Margin="100">
<TextBlock Text="First Name"/>
<TextBox x:Name="firstNameTextBoxView1" Text="{Binding View1InfoClass.FirstName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Last Name"/>
<TextBox x:Name="lastNameTextBoxView1" Text="{Binding View1InfoClass.LastName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Random Useless Number" ></TextBlock>
<TextBox x:Name="randomUselessNumberView1" Text="{Binding View1InfoClass.Number, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="First Name Entered"></TextBlock>
<TextBlock Text="{Binding View1InfoClass.FirstName}"></TextBlock>
<TextBlock Text="Last Name Entered" ></TextBlock>
<TextBlock Text="{Binding View1InfoClass.LastName}"></TextBlock>
<TextBlock Text="Random Useless Number Entered"></TextBlock>
<TextBlock Text="{Binding View1InfoClass.Number}"></TextBlock>
<Button DataContext="{DynamicResource AppVMinView1}" Content="Go to view2" Height="20" Width="70" Command="{Binding View2ButtonCommand}" />
</StackPanel>
</Grid>
根据我所阅读的内容(此处和互联网),我需要将视图设为单例,我尝试这样做的方式是为 view2 声明一个静态 属性 初始化为一个新的 view2与私人 setter,但这并没有削减它。如果有任何帮助,我将不胜感激。
我还应该补充一点,即使 view1 中更改为 view2 的按钮不起作用,侧面导航栏按钮也可以正常工作。
您似乎正在创建 AppVm
的多个实例。
View1
中的按钮和导航栏的按钮显然没有绑定到 AppVm
的同一个实例。
同样适用于 CurrentView
属性:您的内容主机绑定到不同的实例,即不同的 属性 值引用,而不是您在 View1
中修改的 CurrentView
。因此,从 View1
内部更改 CurrentView
对内容主机没有影响 --> 视图永远不会改变。
确保您始终在同一上下文中引用同一(共享)实例。
根据您 UI 的结构,有多种方法可以实现此目的。创建视图模型 class 的单例是迄今为止最糟糕的选择。应该并且可以始终避免单身人士。
最简单的解决方案是在应用程序中将视图模型声明为资源,xaml ResourceDictionary
:
App.xaml
通过 StaticResource
标记扩展资源字典查找,此文件中定义的资源可在任何 XAML 上下文中全局使用。
<Application>
<Application.Resources>
<AppVM x:Key="AppVMinView1" />
</Application.Resources>
</Application>
在任何 XAML 文件中(应用范围):
<UserControl>
<UserControl.DataContext>
<View1VM />
</UserControl.DataContext>
<!-- Reference resources defined in App.xaml, using the StaticResource markup extension -->
<Button Command="{Binding Source={StaticResource AppVMinView1}, Path=View2ButtonCommand}" />
</UserControl>
推荐的解决方案
看起来您正在视图模型中创建视图或页面的实例(我假设 new View1(dummy as string)
创建了一个控件,因为视图模型被命名为 View1VM
)。仅使用视图模型也可以以更优雅的方式解决您的问题。
使用页面视图模型的单个实例非常重要,以防您不想在切换视图时丢失状态(和数据)。 (不要将其与 Singleton 混淆,这是一种设计模式,通过将其分配给 static
属性 来确保 global 使用单个实例.单例模式通常被认为是一种反模式。)
这是一个关于如何显示和导航页面的简短但完整的示例:
AppVM.cs
// Main view model
class AppVM : ObservableObject
{
// Create a property that controls current view
private ObservableObject _currentView;
public ObservableObject CurrentView
{
get => _currentView;
private set => OnPropertyChanged(ref _currentView, value);
}
private Dictionary<string, ObservableObject> Pages { get; set; }
public AppVM()
{
// Create and store the pages,
// so that the same instances can be reused.
// All pages must extend ObservableObject (or any other common base type).
this.Pages = new Dictionary<string, ObservableObject>()
{
{ nameof(DefaultVM), new DefaultVM() },
{ nameof(View1VM), new View1VM() },
{ nameof(View2VM), new View2VM() },
};
// Initialize first page
this.CurrentView = this.Pages[nameof(DefaultVM)];
this.DefaultCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(DefaultVM)], param => true);
this.View1ButtonCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(View1VM)], param => true);
this.View2ButtonCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(View2VM)], param => true);
}
}
View1.xaml
<!-- DataContext is inherited from the surrounding DataTemplate and is the corresponding page view model -->
<UserControl>
<StackPanel>
<TextBox Text="{Binding View1InfoClass.FirstName}" />
<!--
Bind to the command of the same view model instance,
which is the DataContext of the content host
-->
<Button Content="Show View2"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=MainWindow}, Path=DataContext.View2ButtonCommand}" />
</StackPanel>
</UserControl>
MainWindow.xaml
<Window>
<Window.DataContext>
<AppVM />
</Window.DataContext>
<Window.Resources>
<!-- Define the views as implicit (keyless) DataTemplate -->
<DataTemplate DataType="{x:Type DefaultVM}">
<DefaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type View1VM}">
<View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type View2VM}">
<View2 />
</DataTemplate>
</Window.Resources>
<!--
Host of the pages.
The implicit DataTemplates will apply automatically
and show the control that maps to the current CurrentView view model
-->
<ContentPresenter Content="{Binding CurrentView}" />
</Window>
我有一个主 window,带有用于导航的侧边栏和一个用户控件,我在其中显示 3 个视图(默认视图 1、视图 2)。在主视图模型(称为 AppVM)中,我将 contentcontrol 初始化为默认视图,该视图有一个按钮可以进入 view1(除了导航侧边栏)。我在 AppVM 中有命令可以切换到三个视图中的任何一个。 View1 然后有另一个按钮应该移动到 view2(使用主视图模型中存在的命令)。但是,每当我按下 view1 中的按钮(移动到 view2)时,显示都不会改变。奇怪的是,在调试时,当按下 view1 中的按钮时,内容控件绑定到的变量被设置为默认视图,而不是当前视图即 view1。
我认为我设置命令的方式会创建内容控件绑定变量的新实例,但我不知道如何让它使用相同的实例而不是一次又一次地打开新实例。
主视图模型 (AppVM)
public class AppVM : ObservableObject
{
//Create a property that controls current view
private object _currentView;
public object CurrentView
{
get { return _currentView; }
private set
{
OnPropertyChanged(ref _currentView, value);
}
}
private string _textboxText;
public string TextboxText
{
get { return _textboxText; }
set
{
OnPropertyChanged(ref _textboxText, value);
}
}
//Instantiate the relaycommands, we will need to instantiate relaycommand objects for every command we need to perform.
//This means that we will need to do this for preses of all buttons
public RelayCommand View1ButtonCommand { get; private set; }
public RelayCommand View2ButtonCommand { get; private set; }
public RelayCommand DefaultCommand { get; private set; }
public AppVM()
{
//CurrentView = this;
CurrentView = new DefaultVM();
View1ButtonCommand = new RelayCommand(ShowView1, AlwaysTrueCommand);
View2ButtonCommand = new RelayCommand(ShowView2, AlwaysTrueCommand);
DefaultCommand = new RelayCommand(ShowDefault, AlwaysTrueCommand);
}
public void ShowDefault(object dummy)
{
// CurrentView = null;
CurrentView = new DefaultVM();
}
public void ShowView1(object dummy)
{
//CurrentView = null;
CurrentView = new View1(dummy as string);
}
public void ShowView2(object dummy)
{
// CurrentView = null;
CurrentView = new View2();
}
public bool AlwaysTrueCommand(object dummy)
{
return true;
}
}
View1 虚拟机
public class View1VM : ObservableObject {
public InfoClass View1InfoClass { get; set; }
public View1VM() { View1InfoClass = new InfoClass //Apparently I need to instantiate and initialize this to activate binding {
FirstName = "Abbas",
//FirstName = passedInforClass,
LastName = "Syed",
Number = 12
};
} }
view1.xaml中的命令
<UserControl.Resources>
<vm:AppVM x:Name="AppVMinView1" x:Key="AppVMinView1"></vm:AppVM>
</UserControl.Resources>
<UserControl.DataContext>
<vm:View1VM></vm:View1VM>
</UserControl.DataContext>
<Grid Background="Aqua">
<StackPanel Margin="100">
<TextBlock Text="First Name"/>
<TextBox x:Name="firstNameTextBoxView1" Text="{Binding View1InfoClass.FirstName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Last Name"/>
<TextBox x:Name="lastNameTextBoxView1" Text="{Binding View1InfoClass.LastName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Random Useless Number" ></TextBlock>
<TextBox x:Name="randomUselessNumberView1" Text="{Binding View1InfoClass.Number, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="First Name Entered"></TextBlock>
<TextBlock Text="{Binding View1InfoClass.FirstName}"></TextBlock>
<TextBlock Text="Last Name Entered" ></TextBlock>
<TextBlock Text="{Binding View1InfoClass.LastName}"></TextBlock>
<TextBlock Text="Random Useless Number Entered"></TextBlock>
<TextBlock Text="{Binding View1InfoClass.Number}"></TextBlock>
<Button DataContext="{DynamicResource AppVMinView1}" Content="Go to view2" Height="20" Width="70" Command="{Binding View2ButtonCommand}" />
</StackPanel>
</Grid>
根据我所阅读的内容(此处和互联网),我需要将视图设为单例,我尝试这样做的方式是为 view2 声明一个静态 属性 初始化为一个新的 view2与私人 setter,但这并没有削减它。如果有任何帮助,我将不胜感激。
我还应该补充一点,即使 view1 中更改为 view2 的按钮不起作用,侧面导航栏按钮也可以正常工作。
您似乎正在创建 AppVm
的多个实例。
View1
中的按钮和导航栏的按钮显然没有绑定到 AppVm
的同一个实例。
同样适用于 CurrentView
属性:您的内容主机绑定到不同的实例,即不同的 属性 值引用,而不是您在 View1
中修改的 CurrentView
。因此,从 View1
内部更改 CurrentView
对内容主机没有影响 --> 视图永远不会改变。
确保您始终在同一上下文中引用同一(共享)实例。
根据您 UI 的结构,有多种方法可以实现此目的。创建视图模型 class 的单例是迄今为止最糟糕的选择。应该并且可以始终避免单身人士。
最简单的解决方案是在应用程序中将视图模型声明为资源,xaml ResourceDictionary
:
App.xaml
通过 StaticResource
标记扩展资源字典查找,此文件中定义的资源可在任何 XAML 上下文中全局使用。
<Application>
<Application.Resources>
<AppVM x:Key="AppVMinView1" />
</Application.Resources>
</Application>
在任何 XAML 文件中(应用范围):
<UserControl>
<UserControl.DataContext>
<View1VM />
</UserControl.DataContext>
<!-- Reference resources defined in App.xaml, using the StaticResource markup extension -->
<Button Command="{Binding Source={StaticResource AppVMinView1}, Path=View2ButtonCommand}" />
</UserControl>
推荐的解决方案
看起来您正在视图模型中创建视图或页面的实例(我假设 new View1(dummy as string)
创建了一个控件,因为视图模型被命名为 View1VM
)。仅使用视图模型也可以以更优雅的方式解决您的问题。
使用页面视图模型的单个实例非常重要,以防您不想在切换视图时丢失状态(和数据)。 (不要将其与 Singleton 混淆,这是一种设计模式,通过将其分配给 static
属性 来确保 global 使用单个实例.单例模式通常被认为是一种反模式。)
这是一个关于如何显示和导航页面的简短但完整的示例:
AppVM.cs
// Main view model
class AppVM : ObservableObject
{
// Create a property that controls current view
private ObservableObject _currentView;
public ObservableObject CurrentView
{
get => _currentView;
private set => OnPropertyChanged(ref _currentView, value);
}
private Dictionary<string, ObservableObject> Pages { get; set; }
public AppVM()
{
// Create and store the pages,
// so that the same instances can be reused.
// All pages must extend ObservableObject (or any other common base type).
this.Pages = new Dictionary<string, ObservableObject>()
{
{ nameof(DefaultVM), new DefaultVM() },
{ nameof(View1VM), new View1VM() },
{ nameof(View2VM), new View2VM() },
};
// Initialize first page
this.CurrentView = this.Pages[nameof(DefaultVM)];
this.DefaultCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(DefaultVM)], param => true);
this.View1ButtonCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(View1VM)], param => true);
this.View2ButtonCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(View2VM)], param => true);
}
}
View1.xaml
<!-- DataContext is inherited from the surrounding DataTemplate and is the corresponding page view model -->
<UserControl>
<StackPanel>
<TextBox Text="{Binding View1InfoClass.FirstName}" />
<!--
Bind to the command of the same view model instance,
which is the DataContext of the content host
-->
<Button Content="Show View2"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=MainWindow}, Path=DataContext.View2ButtonCommand}" />
</StackPanel>
</UserControl>
MainWindow.xaml
<Window>
<Window.DataContext>
<AppVM />
</Window.DataContext>
<Window.Resources>
<!-- Define the views as implicit (keyless) DataTemplate -->
<DataTemplate DataType="{x:Type DefaultVM}">
<DefaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type View1VM}">
<View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type View2VM}">
<View2 />
</DataTemplate>
</Window.Resources>
<!--
Host of the pages.
The implicit DataTemplates will apply automatically
and show the control that maps to the current CurrentView view model
-->
<ContentPresenter Content="{Binding CurrentView}" />
</Window>