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 应用程序:

  1. 为您要导航到的每个屏幕创建用户控件。在前面的示例中,您可以为任务列表模块创建一个 UserControl,为我的议程模块创建另一个 UserControl。检查 this link 以便您了解什么是 UserControl。
  2. 管理主导航window。就像在 WinForms 中一样,您可以处理左侧面板中每个按钮的点击事件,但是,处理点击事件的一种优雅方式是在 parent 容器中处理它,因为与 Winforms 不同,点击事件是冒泡事件。勾选thislink,就知道什么是路由事件,什么是冒泡事件了。
  3. 在示例视频中,您是否注意到每个模块都位于一个具有 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
}