WPF 中的动态控件切换
Dynamic controls switching in WPF
标题可能有误导性,但我不确定如何描述它。
假设我有 2 个容器 - 一个在左边,一个在右边。左容器有多个按钮。按下它们将改变第二个容器内的内容。
如果我按下第一个按钮,将出现一组按钮和日历,第二个 - datagridview 等。它的例子。
我该如何实现?我不是在寻求解决方案(显然不能用一行代码解决),但我应该搜索什么。一些具体的控制?显示其他window里面呢?等等
您可以将数据模板与数据绑定一起使用:https://docs.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview
这将允许您定义自动应用于特定类型对象的模板。因此,您可以单独应用日历对象、列表视图、数据网格等。
您还可以在单击按钮时根据需要使用 show/hide 视图的可见性。
MVVM 框架经常使用这个:https://compiledexperience.com/blog/posts/using-caliburn-micro-as-a-data-template-selector
另一个例子https://www.codemag.com/article/0907111/Dressing-Up-Your-Data-with-WPF-DataTemplates
还有其他 MVVM 方法使用激活器 show/hide/生成特定类型的新对象并显示它们。
不知道自己有没有看懂问题,所以根据自己的理解写了下面的场景
如您所述,您有一个包含 2 个面板的主 window,一个在左侧,另一个在右侧。在左侧面板中,有一个按钮列表作为一组菜单放置,单击时会在右侧面板中显示其他内容,类似于到另一个系统模块的导航(参见 gif):
如果这是您的情况,您可以按如下方式设计 WPF 应用程序:
- 为您要导航到的每个屏幕创建用户控件。在前面的示例中,您可以为任务列表模块创建一个 UserControl,为我的议程模块创建另一个 UserControl。检查 this link 以便您了解什么是 UserControl。
- 管理主导航window。就像在 WinForms 中一样,您可以处理左侧面板中每个按钮的点击事件,但是,处理点击事件的一种优雅方式是在 parent 容器中处理它,因为与 Winforms 不同,点击事件是冒泡事件。勾选thislink,就知道什么是路由事件,什么是冒泡事件了。
- 在示例视频中,您是否注意到每个模块都位于一个具有 header 的容器中,并且当单击按钮时 header 文本会发生变化,并且 header文本是用按钮文本更新的吗?这可以通过多种方式完成,但一个好的方式是通过数据绑定,检查 this link 以了解这个概念是什么。有了经验,您就会意识到什么时候应用它是明智的,什么时候不适合。
如您所见,您应该复习和学习许多概念,以便能够利用 WPF 的所有优势设计出良好的应用程序,并继续 philosophy WPF 的。
我写了一个示例代码,我也发布在 GitHub 上。我解释了有关代码的一些内容,但我建议您在我留给您的 link 和其他可靠的知识来源(例如 Microsoft 本身的书籍或教程)中扩展这些概念。
Xaml主窗口:
<Window
x:Class="WpfApp26.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:local="clr-namespace:WpfApp26"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800" Height="450"
d:DataContext="{d:DesignInstance Type=local:ViewModel}"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<!-- A GroupBox is a control with a header -->
<GroupBox Header="Options">
<!-- Look that the click event is handled in the StackPanel, the container for the buttons -->
<StackPanel Button.Click="ModuleSelected_OnClick">
<Button
Margin="5" Padding="5"
Content="To Do List" Tag="ToDoListModule" />
<Button
Margin="5" Padding="5"
Content="My Agenda" Tag="MyAgendaModule" />
</StackPanel>
</GroupBox>
<!-- The header property is binding to the CurrentModuleName property in the DataContext -->
<GroupBox Name="GbCurrentModule" Grid.Column="1" Header="{Binding CurretModuleName}" />
</Grid>
</Window>
MainWindow背后的代码(复习INotifyProperyChanged):
public partial class MainWindow : Window {
private readonly ViewModel vm;
public MainWindow() {
InitializeComponent();
// Setting the Window's DataContext to a object of the ViewModel class.
this.DataContext = this.vm = new ViewModel();
}
private void ModuleSelected_OnClick(object sender, RoutedEventArgs e) {
// The Source property of the RoutedEventArgs gets the Element that fires the event (in this case, the button).
var clickedButton = (Button) e.Source;
this.vm.CurretModuleName = clickedButton.Content.ToString();
// Getting the Tag property of the button.
var tag = clickedButton.Tag.ToString();
// Performing the navigation.
switch (tag) {
case "ToDoListModule":
NavigateToModule(new UcToDoListModule());
break;
case "MyAgendaModule":
NavigateToModule(new UcMyAgendaModule());
break;
}
#region Internal methods
void NavigateToModule(UserControl uc) {
this.GbCurrentModule.Content = uc;
}
#endregion
}
}
ViewModel class:
// The class implementents the INotifyPropertyChanged interface, that is used
// by the WPF notifications system.
public class ViewModel : INotifyPropertyChanged {
private string curretModuleName;
public string CurretModuleName {
get => this.curretModuleName;
set {
this.curretModuleName = value;
this.OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
标题可能有误导性,但我不确定如何描述它。
假设我有 2 个容器 - 一个在左边,一个在右边。左容器有多个按钮。按下它们将改变第二个容器内的内容。 如果我按下第一个按钮,将出现一组按钮和日历,第二个 - datagridview 等。它的例子。
我该如何实现?我不是在寻求解决方案(显然不能用一行代码解决),但我应该搜索什么。一些具体的控制?显示其他window里面呢?等等
您可以将数据模板与数据绑定一起使用:https://docs.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview
这将允许您定义自动应用于特定类型对象的模板。因此,您可以单独应用日历对象、列表视图、数据网格等。
您还可以在单击按钮时根据需要使用 show/hide 视图的可见性。
MVVM 框架经常使用这个:https://compiledexperience.com/blog/posts/using-caliburn-micro-as-a-data-template-selector
另一个例子https://www.codemag.com/article/0907111/Dressing-Up-Your-Data-with-WPF-DataTemplates
还有其他 MVVM 方法使用激活器 show/hide/生成特定类型的新对象并显示它们。
不知道自己有没有看懂问题,所以根据自己的理解写了下面的场景
如您所述,您有一个包含 2 个面板的主 window,一个在左侧,另一个在右侧。在左侧面板中,有一个按钮列表作为一组菜单放置,单击时会在右侧面板中显示其他内容,类似于到另一个系统模块的导航(参见 gif):
如果这是您的情况,您可以按如下方式设计 WPF 应用程序:
- 为您要导航到的每个屏幕创建用户控件。在前面的示例中,您可以为任务列表模块创建一个 UserControl,为我的议程模块创建另一个 UserControl。检查 this link 以便您了解什么是 UserControl。
- 管理主导航window。就像在 WinForms 中一样,您可以处理左侧面板中每个按钮的点击事件,但是,处理点击事件的一种优雅方式是在 parent 容器中处理它,因为与 Winforms 不同,点击事件是冒泡事件。勾选thislink,就知道什么是路由事件,什么是冒泡事件了。
- 在示例视频中,您是否注意到每个模块都位于一个具有 header 的容器中,并且当单击按钮时 header 文本会发生变化,并且 header文本是用按钮文本更新的吗?这可以通过多种方式完成,但一个好的方式是通过数据绑定,检查 this link 以了解这个概念是什么。有了经验,您就会意识到什么时候应用它是明智的,什么时候不适合。
如您所见,您应该复习和学习许多概念,以便能够利用 WPF 的所有优势设计出良好的应用程序,并继续 philosophy WPF 的。
我写了一个示例代码,我也发布在 GitHub 上。我解释了有关代码的一些内容,但我建议您在我留给您的 link 和其他可靠的知识来源(例如 Microsoft 本身的书籍或教程)中扩展这些概念。
Xaml主窗口:
<Window
x:Class="WpfApp26.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:local="clr-namespace:WpfApp26"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800" Height="450"
d:DataContext="{d:DesignInstance Type=local:ViewModel}"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<!-- A GroupBox is a control with a header -->
<GroupBox Header="Options">
<!-- Look that the click event is handled in the StackPanel, the container for the buttons -->
<StackPanel Button.Click="ModuleSelected_OnClick">
<Button
Margin="5" Padding="5"
Content="To Do List" Tag="ToDoListModule" />
<Button
Margin="5" Padding="5"
Content="My Agenda" Tag="MyAgendaModule" />
</StackPanel>
</GroupBox>
<!-- The header property is binding to the CurrentModuleName property in the DataContext -->
<GroupBox Name="GbCurrentModule" Grid.Column="1" Header="{Binding CurretModuleName}" />
</Grid>
</Window>
MainWindow背后的代码(复习INotifyProperyChanged):
public partial class MainWindow : Window {
private readonly ViewModel vm;
public MainWindow() {
InitializeComponent();
// Setting the Window's DataContext to a object of the ViewModel class.
this.DataContext = this.vm = new ViewModel();
}
private void ModuleSelected_OnClick(object sender, RoutedEventArgs e) {
// The Source property of the RoutedEventArgs gets the Element that fires the event (in this case, the button).
var clickedButton = (Button) e.Source;
this.vm.CurretModuleName = clickedButton.Content.ToString();
// Getting the Tag property of the button.
var tag = clickedButton.Tag.ToString();
// Performing the navigation.
switch (tag) {
case "ToDoListModule":
NavigateToModule(new UcToDoListModule());
break;
case "MyAgendaModule":
NavigateToModule(new UcMyAgendaModule());
break;
}
#region Internal methods
void NavigateToModule(UserControl uc) {
this.GbCurrentModule.Content = uc;
}
#endregion
}
}
ViewModel class:
// The class implementents the INotifyPropertyChanged interface, that is used
// by the WPF notifications system.
public class ViewModel : INotifyPropertyChanged {
private string curretModuleName;
public string CurretModuleName {
get => this.curretModuleName;
set {
this.curretModuleName = value;
this.OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}