Search code examples
c#wpfwindowscontrols

Dynamic controls switching in WPF


Title might be misleading but i'm not sure how to describe it.

Lets say i have 2 containers - one on the left, one on the right. Left container has multiple buttons. Pressing them will change whats inside 2nd container. If i press 1st button a set of buttons and calendar will appear, 2nd - datagridview etc. Its example.

How can i achieve it? I'm not asking for solution (it can't be solved in one line of code, obviously), but what should i search for. Some specific control? Displaying other window inside it? Etc.


Solution

  • I am not sure if I understood the question well, so I wrote the following scenario from what I understood.

    As you mentioned, you have a main window that contains 2 panels, one on the left and the other on the right. In the left panel, there is a list of buttons placed as a group of menus, which, when clicked, show other content in the right panel, something like a navigation to another system module (see the gif):

    Demo

    If this is your scenario, you can design your WPF application as follows:

    1. Create UserControls for each screen you want to navigate to. In the previous example, you could create a UserControl for the module of the task list, and another UserControl for the module of My Agenda. Check this link so you know what a UserControl is.
    2. Manage navigation on the main window. Just like in WinForms, you could handle the click event on each button in the left panel, however, an elegant way to handle the click event is that your handle it in the parent container, since, unlike Winforms, the click event is a bubbling event. Check this link, so you know what a routed event and what is a bubbling event.
    3. In the example video, could you notice that each module is in a container that has a header and that the header text changes when the button is clicked and the header text is updated with the button text? This can be done in many ways, but a good way to do it is through data binding, check this link to understand what this concept is. With experience, you will realize when it will be advisable to apply this and when it will not.

    As you can see, there are many concepts that you should review and learn to be able to make a good design of an application taking advantage of all the benefits that WPF has and to continue with the philosophy of WPF.

    I write an example code that I also publish on GitHub. I explain some things about the code, but I suggest that you expand these concepts in the links that I left you and in other reliable sources of knowledge, such as books or tutorials from Microsoft itself.

    The Xaml MainWindow:

    <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>
    

    The MainWindow code behind (review the 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
        }
    }
    

    The 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
    }