【问题标题】:How can create layout dynamically in WPF (MVVM Pattern)如何在 WPF 中动态创建布局(MVVM 模式)
【发布时间】:2018-01-04 07:09:06
【问题描述】:

如何在 WPF (MVVM Pattern) 中动态创建布局?场景如下:

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

如何在 WPF 中做到这一点?

【问题讨论】:

  • 尝试使用Grid 及其ColumnDefinitions 和RowDefinitions。
  • 甚至是stackpanel

标签: wpf mvvm binding


【解决方案1】:

使用列表视图并将项目面板自定义为 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; }
}

【讨论】:

  • Greate,非常感谢,您的观点解决了我的问题,我还为 UniformGrid 的行数和列数添加了绑定,可以更准确地管理它们。再次感谢您。
【解决方案2】:

好的,这只是一个小例子。我使用了一个视图模型,实现了 InotifyPropertyChanged,一个绑定了命令并在网格列上触发的按钮。这是工作示例

  • 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 个文本块来代表您的“相机”

现在是视图模型

-视图模型

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,您不应该对我上面介绍的视图模型感到惊讶

作为额外的实用程序,我用来实现命令

-中继命令

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.:有人会告诉你,像我一样分配数据上下文是你在 MVVM 中可能犯的最大错误。我不同意这一点,但是,在使用 MVVM 时,您必须找到自己的方式,并且我使用 datacontext 只是为了更快地编写此示例

【讨论】:

  • 如果相机的集合超过3个怎么办? 6个甚至9个!为什么不使用集合来存储相机?这看起来像WinForms 方法,通过将 Width 设置为 0!谁做的?!在您添加的每个摄像头的示例中,您必须添加另一个列定义,DataTemplate 是这里应该使用的。关于DataContext,您没有解释为什么您不同意该声明。
  • 首先,冷静下来。第二个@XAMlMAX,该操作非常含糊,并要求提供建议。我构建了一个示例只是为了说明它可以做什么。在没有 OP 任何输入的情况下从 0 开始构建应用程序,这是一项漫长的工作,甚至不应该完成。当然,事情可以做得更好,当然必须这样做,以便您可以添加更多相机(即使添加太多相机没有用,以便您可以以某种方式预测最大数量)。如果您愿意,下次构建您自己的答案。
  • @Daniele Sartori,谢谢,我们不知道要添加多少个摄像头,所以我们不能只为网格定义 2 列,您的示例代码仅适用于 2 个静态摄像头。还有,例如如果摄像头数量达到4,屏幕应该分为2行2列。
  • @Chad 再次,因为你没有发布任何关于你的代码的内容,我继续写一个非常基本的例子来说明它可以做什么。正如 XAMIMAX 在他的评论中所说,您可以使用一组摄像头和一个模板来根据当前激活的摄像头数量来更改 UI。
  • @Daniele Sartori,你将给你一个很棒的想法,再次感谢你,非常感谢你花费你的时间,这对我来说非常有价值。
猜你喜欢
  • 1970-01-01
  • 2021-10-06
  • 2011-07-13
  • 2014-09-14
  • 2021-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-08
相关资源
最近更新 更多