如何在WPF(MVVM模式)中动态创建布局?情景如下:
像摄像机查看器的应用程序, 启动时有一个主视图,屏幕顶部有一个带标签的按钮("添加摄像头"),当添加摄像头时,它将在整个主屏幕上显示,选择第二个摄像头后,屏幕应分为两部分,选择第三部相机后,屏幕应分为第三部分等。
如何在WPF中执行此操作?
答案 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,因为我只是为了更快地编写这个例子