1

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?

2
  • Try playing around with a Grid and its ColumnDefinitions & RowDefinitions. Commented Jan 4, 2018 at 7:47
  • Or even with a stackpanel Commented Jan 4, 2018 at 9:03

2 Answers 2

4

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; }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Greate, Thank you so much, your point is resolved my issue, also I added binding for count of Rows and Columns of UniformGrid which can manage them more accurately. thank you again.
0

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

6 Comments

What if the collection of the cameras is more than 3? 6 or even 9! Why not using a collection to store the cameras? This looks like WinForms approach, by setting Width to 0! Who does that?! In your example for every camera added you will have to add another column definition, DataTemplate is what should be used here. And in regards to the DataContext you have not explained why you don't agree with that statement.
First of all, calm down. Second @XAMlMAX, the op was pretty vague and asked for suggestion. I builded an example just to give an idea of what it can be done. Build an app from 0 without any input by the OP it's a long work, and it shouldn't even be done. Of course the things can be done better and of course it must be done so that you can add more cameras (even if it's useless to add too many cameras so that you can somehow foresee a max number). Next time build your own answer if you want.
@Daniele Sartori,Thank you, We don't know how many camera will be added, so we can not define just 2 column for grid , Your sample code is for only 2 static camera. and also, for example if count of camera reaches to 4, the screen should divided to 2 rows and 2 columns.
@Chad again, since you didn't post anything about your code, i proceed to write a very basic example of what it can be done. As XAMIMAX stated in his comment, you can make use of a collection of cameras, and a template, to change the UI accordingly to how many camera are currently activated.
@Daniele Sartori, you were going to give you an idea that was great, Thank you again, and thank you very much for having spent your time and this is very valuable to me.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.