Reputation: 123
I am trying to add one child element to Grid control through binding in mvvm pattern instead of code behind.
View Code:
<Grid Name="grid">
</Grid>
Code behind:
public MainWindow()
{
InitializeComponent();
AddControl();
}
private void AddControl()
{
Viewer viewer = new Viewer();
grid.Children.Add(viewer.GetUIElement());
}
The above code works fine but I want to do this in VM through binding instead of code behind. Please note that, in above code Viewer is the third party control and it add to the grid perfectly through code behind. I want to add Viewer control to the Grid dynamically using binding.
It is not always necessary to have a Grid control there. It can be any control like a parent/hosting/content control. There is only one child control that is Viewer.
I have a basic knowledge of binding like ICommand, INotifyPropertyChanged, Dependency Property etc., but now aware about how to add any control(Viewer in my case) into another parent control(Grid in my case).
The code may be following: View and VM is bound through DataContext
View Code:
<ContentControl Content="{Binding MyControl}"></ContentControl>
VM Code:
class VM
{
Viewer view;
public VM()
{
view = new Viewer();
}
public Viewer MyControl
{
get {return view; }
set{view= value;}
}
}
Upvotes: 6
Views: 4023
Reputation: 123
As I posted sample code in my question, I have tried the same and by doing some correction in the same code, I found the solution. The only problem was that I wanted to write a property of UIElement type instead of Viewer type.
View Code:
<ContentControl Content="{Binding MyControl}"></ContentControl>
VM Code:
class VM
{
Viewer view;
public VM()
{
view = new Viewer();
}
public UIElement MyControl
{
get {return view.GetUIElement(); }//Set is not necessary
}
}
As in case of code behind with Grid, I have not directly added Viewer control as a child for the Grid but received its UIElement and then it added to the grid. Same way I have not directly written property of Viewer type but written the property of UIElement type.
In such a way, I found the solution of how to add any control into parent control dynamically in WPF using MVVM pattern.
Upvotes: 2
Reputation: 150
The MVVM patternt implies the existence of a ViewModel, which would be the DataContext of your View, and which contains properties you will be binding to, from your View. When you say "child element", I guess you mean something that can be contained in a Grid cell. That can be anything, and depending on the type, you would then need a corresponding ViewModel property. For example, if you need a textblock in your Grid, you would use a string as a property. If you have specific element per grid cell you want to add, you could make it with Collapsed Vilibility, and you could set the Visibility to Visible, when you want. If you have a ListView in a Grid cell, you could bind its ItemsSource to a collection of strings in your ViewModel, and then just add items to your collection. Both examples follow...
View
Window x:Class="WpfApp6.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:local="clr-namespace:WpfApp6"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MyProperty1}"/>
<TextBlock Grid.Row="1" Text="{Binding MyProperty2}" Visibility="{Binding IsMyProperty2Visible, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Button Grid.Column="1" Content="Add Element" Command="{Binding AddElement}"/>
<Button Grid.Row="1" Grid.Column="1" Content="Add ListItem" Command="{Binding AddListItem}"/>
<ListView Grid.Row="2" Grid.Column="1" ItemsSource="{Binding MyCollection}"/>
</Grid>
</Window>
ViewModel
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp6
{
class ViewModel : BaseViewModel
{
private string myProperty1;
private string myProperty2;
private bool isMyProperty2Visible = false;
private ObservableCollection<string> myCollection = new ObservableCollection<string>();
public ObservableCollection<string> MyCollection
{
get
{
return myCollection;
}
set
{
if (value != myCollection)
{
myCollection = value;
RaisePropertyChanged("MyCollection");
}
}
}
public RelayCommand AddElement { get; }
public RelayCommand AddListItem { get; }
public ViewModel()
{
AddElement = new RelayCommand(OnAddElement, CanAddElement);
AddListItem = new RelayCommand(OnAddListItem, CanAddListItem);
}
private bool CanAddListItem()
{
return true;
}
private void OnAddListItem()
{
MyCollection.Add("NewItem");
}
private bool CanAddElement()
{
return true;
}
private void OnAddElement()
{
IsMyProperty2Visible = true;
}
public bool IsMyProperty2Visible
{
get
{
return isMyProperty2Visible;
}
set
{
if (value != isMyProperty2Visible)
{
isMyProperty2Visible = value;
RaisePropertyChanged("IsMyProperty2Visible");
}
}
}
public string MyProperty1
{
get
{
myProperty1 = "Property1";
return myProperty1;
}
set
{
if (value != myProperty1)
{
myProperty1 = value;
RaisePropertyChanged("MyProperty1");
}
}
}
public string MyProperty2
{
get
{
myProperty2 = "Property2";
return myProperty2;
}
set
{
if (value != myProperty2)
{
myProperty2 = value;
RaisePropertyChanged("MyProperty2");
}
}
}
}
}
BaseViewModel
using System;
using System.ComponentModel;
using System.Diagnostics;
namespace WpfApp6
{
public abstract class BaseViewModel : INotifyPropertyChanged, IDisposable
{
#region DisplayName
/// <summary>
/// Returns the user-friendly name of this object.
/// Child classes can set this property to a new value,
/// or override it to determine the value on-demand.
/// </summary>
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
System.Diagnostics.Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void RaisePropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region IDisposable Members
/// <summary>
/// Invoked when this object is being removed from the application
/// and will be subject to garbage collection.
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
#if DEBUG
/// <summary>
/// Useful for ensuring that ViewModel objects are properly garbage collected.
/// </summary>
~BaseViewModel()
{
string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
System.Diagnostics.Debug.WriteLine(msg);
}
#endif
#endregion // IDisposable Members
}
}
Upvotes: 0
Reputation: 12276
I'm not sure why you'd want to have the grid at all or even add whatever the viewer is dynamically.
If this is only ever one child item and it can be different things then you could replace the grid with a contentcontrol.
Bind the content property of that to a property in the window viewmodel. Switch out a viewmodel and datatemplate that into ui.
This approach is called viewmodel first and is often used for navigation.
Some code illustrating how it works:
Define datatemplates:
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UserViewModel}">
<local:UserUC/>
</DataTemplate>
</Window.Resources>
Whenever you give the UI an instance of LoginViewModel that will then be templated out into a LoginUC with that instance of LoginViewModel as a datacontext. Similarly UserViewModel gives you a UserUC in the view.
Bind content of a contentcontrol:
<ContentControl Content="{Binding CurrentViewModel}" />
In the window's viewmodel I have a property CurrentViewModel. The sample sets that to an instance of one of those viewmodels to navigate.
Upvotes: 4
Reputation: 71
Follow these steps-
Upvotes: 2