Reputation: 133
How can create layout dynamically in WPF (MVVM Pattern)? Scenario is as follows:
Something like a application for camera viewer, At startup there is a main view with a button in the top of screen with label("Add camera"),When camera is added, it will be display in whole of main screen, after selecting second camera, screen should be divided into two parts, after selecting third camera, screen should be divided into third parts and so on.
How can do it in WPF?
Upvotes: 1
Views: 888
Reputation: 1196
Use listview and customize item panel to 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>
Code Behind
/// <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; }
}
Upvotes: 4
Reputation: 1703
Ok this is just a little example. I used a viewmodel, with InotifyPropertyChanged implemented, a button with a Command binded and triggers on the column of the grid. Here is the working example
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>
As you can see i placed 2 textblock in the inner grid to represent your "cameras"
now the 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;
}
}
}
}
If you understand MVVM you shouldn't be surprised by the viewmodel i presented above
as Extra an utility that i use to implement Commands
-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
}
}
Basically everything work around the DataTrigger inserted in the ColumnDefinition style. Note that when you press add camera, the Column will take all the available space thanks to Width = "*", so that every time you add a camera, each one will take the same amount of space. Of course this is just an example and you should work on it to add features like remove camera, add the opposite trigger (to put again the width to 0) etc. , but it's just to give you an idea.
P.S.: Someone will tell you that assign the datacontext as i did, is the biggest mistake you can do in MVVM. I don't agree with this, however, you have to find your way, when working with MVVM and i used datacontext as i did just to write this example faster
Upvotes: 0