How to dynamically add controls to a WrapPanel in another class?

前端 未结 1 2062
小鲜肉
小鲜肉 2020-12-19 20:07

I\'m migrating from VB to C# and decided that WPF would be the best option for me as the programs I\'ve been developing are highly GUI dependant based applications. However,

相关标签:
1条回答
  • 2020-12-19 20:26

    Ok, Im using UserControls instead of Pages to keep them in a single Window. Since you didn't post any XAML, I have no idea what your real need is, but here is my take:

    MultiPageSample.xaml ("Main Window"):

    <Window x:Class="MiscSamples.MultiPageMVVM.MultiPageSample"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:MiscSamples.MultiPageMVVM"
            Title="MultiPageSample" Height="300" Width="300">
        <UniformGrid Rows="1" Columns="2">
            <local:Page1 DataContext="{Binding Page1}"/>
            <local:Page2 DataContext="{Binding Page2}"/>
        </UniformGrid>
    </Window>
    

    Code Behind:

    public partial class MultiPageSample : Window
    {
        public MultiPageSample()
        {
            InitializeComponent();
    
            DataContext = new MultiPageViewModel();
        }
    }
    

    ViewModel:

    public class MultiPageViewModel
    {
        public Page1ViewModel Page1 { get; set; }
    
        public Page2ViewModel Page2 { get; set; }
    
        public MultiPageViewModel()
        {
            Page1 = new Page1ViewModel();
            Page2 = new Page2ViewModel();
    
            Page2.AddNewCommand = new Command(Page1.AddCommand);
        }
    }
    

    Page1:

    <UserControl x:Class="MiscSamples.MultiPageMVVM.Page1"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <ItemsControl ItemsSource="{Binding Commands}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Command="{Binding}" Content="Click Me!"
                            Margin="2"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </UserControl>
    

    Code Behind:

    public partial class Page1 : UserControl
    {
        public Page1()
        {
            InitializeComponent();
        }
    }
    

    Page2:

    <UserControl x:Class="MiscSamples.MultiPageMVVM.Page2"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Button Content="Add New Command (I Mean Button)"
                VerticalAlignment="Center" HorizontalAlignment="Center"
                Command="{Binding AddNewCommand}"/>
    </UserControl>
    

    Code Behind:

    public partial class Page2 : UserControl
    {
        public Page2()
        {
            InitializeComponent();
        }
    }
    

    ViewModel:

    public class Page2ViewModel
    {
        public Command AddNewCommand { get; set; }
    }
    

    Command class (can be found on most MVVM frameworks)

    //Dead-simple implementation of ICommand
        //Serves as an abstraction of Actions performed by the user via interaction with the UI (for instance, Button Click)
        public class Command : ICommand
        {
            public Action Action { get; set; }
    
            public void Execute(object parameter)
            {
                if (Action != null)
                    Action();
            }
    
            public bool CanExecute(object parameter)
            {
                return IsEnabled;
            }
    
            private bool _isEnabled = true;
            public bool IsEnabled
            {
                get { return _isEnabled; }
                set
                {
                    _isEnabled = value;
                    if (CanExecuteChanged != null)
                        CanExecuteChanged(this, EventArgs.Empty);
                }
            }
    
            public event EventHandler CanExecuteChanged;
    
            public Command(Action action)
            {
                Action = action;
            }
        }
    
        public class Command<T>: ICommand
        {
            public Action<T> Action { get; set; }
    
            public void Execute(object parameter)
            {
                if (Action != null && parameter is T)
                    Action((T)parameter);
            }
    
            public bool CanExecute(object parameter)
            {
                return IsEnabled;
            }
    
            private bool _isEnabled;
            public bool IsEnabled
            {
                get { return _isEnabled; }
                set
                {
                    _isEnabled = value;
                    if (CanExecuteChanged != null)
                        CanExecuteChanged(this, EventArgs.Empty);
                }
            }
    
            public event EventHandler CanExecuteChanged;
    
            public Command(Action<T> action)
            {
                Action = action;
            }
        }
    

    Result:

    enter image description here

    Now, the explanation of all this mess:

    First of all, you must leave behind the traditional mentality of Manipulating UI elements in code, and embrace MVVM.

    WPF has very powerful DataBinding capabilities that are utterly absent in ancient dinosaur frameworks.

    Notice how I'm using the reusable Command class (which is kind of a basic part of most MVVM frameworks) to represent the Buttons in the Page1ViewModel. These instances of Command are then added to the ObservableCollection, which in turn notifies WPF when an element is added or removed to it, and thus the UI is automatically updated by the Binding.

    Then, the DataTemplate defined as the ItemTemplate for the ItemsControl in Page1 is used to "render" each item inside the ObservableCollection.

    This is what I refer to when I say WPF needs a really different mindset to work with. This is the default approach to EVERYTHING in WPF. You almost NEVER have the need to reference / create / manipulate UI elements in procedural code. That's what XAML is for.

    Also notice that this could be simplified A LOT by using the same ViewModel for both Pages, but I kept them separate on purpose just to show you this case where you have different ViewModels communicating with each other directly.

    Let me know if you have any doubts.

    0 讨论(0)
提交回复
热议问题