如何在WPF中动态创建布局(MVVM模式)

时间:2018-01-04 07:09:06

标签: wpf mvvm binding

如何在WPF(MVVM模式)中动态创建布局?情景如下:

像摄像机查看器的应用程序, 启动时有一个主视图,屏幕顶部有一个带标签的按钮("添加摄像头"),当添加摄像头时,它将在整个主屏幕上显示,选择第二个摄像头后,屏幕应分为两部分,选择第三部相机后,屏幕应分为第三部分等。

如何在WPF中执行此操作?

2 个答案:

答案 0 :(得分:2)

使用listview并将项目面板自定义为UniformGrid

<ListView ItemsSource="{Binding}" VerticalAlignment="Stretch" FlowDirection="LeftToRight" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Hidden">
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid ClipToBounds="True"></UniformGrid>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.ItemTemplate>
                <DataTemplate >
                    <Border BorderThickness="2">
                        <DockPanel Background="Red" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
                            <TextBlock Text="{Binding Id}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"></TextBlock>
                        </DockPanel>
                    </Border>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

代码背后

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    ObservableCollection<Camera> cameraList = new ObservableCollection<Camera>();

    public MainWindow()
    {
        InitializeComponent();            
        this.DataContext = cameraList;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        int sn = cameraList.Count + 1;
        cameraList.Add(new Camera() { Id = sn.ToString()});
    }
}

public class Camera
{
    public string Id { get; set; }
}

答案 1 :(得分:0)

好的,这只是一个小例子。我使用了一个viewmodel,实现了InotifyPropertyChanged,一个带有Command binded的按钮,触发了网格的列。这是工作示例

  • XAML

    <Window x:Class="WpfApp2.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow">
    <Window.DataContext>
        <local:MyViewModel></local:MyViewModel>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Content="Add Camera" Command="{Binding AddCamera}" Margin="10" VerticalAlignment="Center" FontSize="15" Width="90"  Height="26" FontWeight="DemiBold"/>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions >
                <ColumnDefinition >
                    <ColumnDefinition.Style>
                        <Style TargetType="{x:Type ColumnDefinition}">
                            <Setter Property="Width" Value="0" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Camera1}" Value="True">
                                    <Setter Property="Width" Value="*" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </ColumnDefinition.Style>
                </ColumnDefinition>
                <ColumnDefinition >
                    <ColumnDefinition.Style>
                        <Style TargetType="{x:Type ColumnDefinition}">
                            <Setter Property="Width" Value="0" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Camera2}" Value="True">
                                    <Setter Property="Width" Value="*" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </ColumnDefinition.Style>
                </ColumnDefinition>
            </Grid.ColumnDefinitions>
    
            <TextBlock Text=" CAMERA1" Width="100"  HorizontalAlignment="Center" Height="100" Grid.Column="0"/>
            <TextBlock Text=" CAMERA2" Width="100"  HorizontalAlignment="Center" Height="100" Grid.Column="1"/>
        </Grid>
    </Grid>
    
    
    </Window>
    

正如您所看到的,我在内部网格中放置了2个文本块来表示您的“相机”

现在是viewmodel

-Viewmodel

namespace WpfApp2
{
    public class MyViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        private bool camera1 = false;
        private bool camera2 = false;

        public bool Camera1
        {
            get { return camera1; }
            set
            {
                camera1 = true;
                NotifyPropertyChanged();
            }
        }

        public bool Camera2
        {
            get { return camera2; }
            set
            {
                camera2 = true;
                NotifyPropertyChanged();
            }
        }


        private RelayCommand addCamera;

        private void Add()
        {
            if (Camera1 == false)
            {
                Camera1 = true;
            }
            else
                Camera2 = true;
        }

        public ICommand AddCamera
        {
            get
            {
                addCamera = new RelayCommand(() => Add());
                return addCamera;
            }
        }
    }


}

如果你了解MVVM,你不应该对我上面提到的viewmodel感到惊讶

as Extra我用来实现命令的实用程序

-Relay Command

namespace WpfApp2
{
    internal class RelayCommand<T> : ICommand
    {
        #region Fields

        readonly Action<T> _execute = null;
        readonly Predicate<T> _canExecute = null;

        #endregion // Fields

        #region Constructors

        public RelayCommand(Action<T> execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Creates a new command.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action<T> execute, Predicate<T> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion // Constructors

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute((T)parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }

        #endregion // ICommand Members
    }

    /// <summary>
    /// A command whose sole purpose is to 
    /// relay its functionality to other
    /// objects by invoking delegates. The
    /// default return value for the CanExecute
    /// method is 'true'.
    /// </summary>
    internal class RelayCommand : ICommand
    {
        #region Fields

        readonly Action _execute;
        readonly Func<bool> _canExecute;

        #endregion // Fields

        #region Constructors

        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Creates a new command.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion // Constructors

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute();
        }

        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

        public void Execute(object parameter)
        {
            _execute();
        }

        #endregion // ICommand Members
    }
}

基本上,一切都围绕在ColumnDefinition样式中插入的DataTrigger工作。请注意,当您按下添加摄像头时,由于Width =“*”,列将占用所有可用空间,因此每次添加摄像头时,每个摄像头将占用相同的空间。当然这只是一个例子,您应该使用它来添加删除相机等功能,添加相反的触发器(再次将宽度设置为0)等等,但这只是为了给您一个想法。

P.S。:有人会告诉你像我一样分配datacontext,这是你在MVVM中可以做的最大的错误。我不同意这一点,但是,你必须找到自己的方式,在使用MVVM时我使用了datacontext,因为我只是为了更快地编写这个例子