I have the following process:
Create a new object in a view/viewmodel and save it in the database via a database handler
Database Handler implements INotifyProperyChanged. So after 1. the database handler notifies other viewmodels about the change. One of the corresponding view contains a datagrid which is bound to a ObservableCollection, which is stored in the database. So the view/viewmodel access the database actively.
The creation of the new object (see 1.) changes the database content and should also updating the datagrid view. So the viewmodel is informed about the change via the notification. So the next step would be to access the database again and filling or renew the observable with these new data.
So how to force the refresh of the datagrid content?
Tried following:
Assigning the ObservableCollection temporarly to null does not refresh the datagrid, because it does not notify the datagrid view.
Clearing the Collection and adding all items to the new collection (works, but sounds a bit weired because in most cases I will add simply one object to the database)
Here is some sample code:
First the database handler, which handles the data exchange between viewmodels and database. The DBHandler implements the INotifyPropertyChanged to qualify the viewmodels to react on changes in the database. Currently the DBHandler notifies only if the Names List is changed:
public class DBHandler:INotifyPropertyChanged
{
#region Singleton Pattern
private static DBHandler instance;
private DBHandler()
{
}
public static DBHandler GetInstance()
{
if (instance == null)
instance = new DBHandler();
return instance;
}
#endregion
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
// Represents Sample Data of the database
private List<string> names = new List<string>() { "Sample1", "Sample2" };
public List<string> Names
{
get { return names; }
}
/// <summary>
/// Saves a new name in the database
/// </summary>
/// <param name="name"></param>
public void AddNewName(string name)
{
names.Add(name);
NotifyPropertyChanged();
}
}
The MainWindowViewModels can save a new name via the DBHandler, and listens for changes of List DBHandler.Names by using
public class MainWindowViewModel
{
#region Constructors
public MainWindowViewModel()
{
// Initialize the command for the add button click
addName = new AddNameCommand();
// Assign database collection entries
names = new ObservableCollection<string>(DBHandler.GetInstance().Names);
DBHandler.GetInstance().PropertyChanged += MainWindowViewModel_PropertyChanged_Names;
}
/// <summary>
/// Listen for the DBHandler.Names change for updating the datagrid view.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainWindowViewModel_PropertyChanged_Names(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "Names")
{
// Try to update the datagrid view
// First Try: Reassign
names = new ObservableCollection<string>(DBHandler.GetInstance().Names);
}
}
#endregion
private ObservableCollection<string> names;
public ObservableCollection<string> Names
{
get { return names; }
set { names = value; }
}
#region Commands
/// <summary>
/// Command for adding the textbox content as new name to the database
/// </summary>
public class AddNameCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
Debug.WriteLine("CanExecute");
return ((string)parameter) != "" || parameter != null;
}
public void Execute(object parameter)
{
// Save the name in the database
DBHandler.GetInstance().AddNewName((string)parameter);
}
}
AddNameCommand addName; // Instance of the command which will be intialized in the constructor
public ICommand btnClickAdd
{
get {
Debug.WriteLine("btnClickAdd");
return (ICommand) addName; }
}
#endregion
}
Last the view contains a TextBox for the name which will be saved by a button click and a DataGrid for displaying all names in the database. So the DataGrid is bounded to the ObservableCollection of Names in the viewmodel.
<Window.Resources>
<local:MainWindowViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid>
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Source={StaticResource ViewModel}, Path=Names}" HorizontalAlignment="Left" Margin="48,142,0,0" VerticalAlignment="Top" Height="127" Width="422"/>
<Button x:Name="button_AddName" Command="{Binding Source={StaticResource ViewModel}, Path=btnClickAdd}" Content="Add" HorizontalAlignment="Left" Margin="331,61,0,0" VerticalAlignment="Top" Width="75" CommandParameter="{Binding Text, ElementName=textBox_Name}"/>
<TextBox x:Name="textBox_Name" HorizontalAlignment="Left" Height="23" Margin="160,58,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</Grid>
Delete most of DBHandler, it's just confusing you. All you want to do is retrieve DB stuff when requested, and save it when told. It sits there waiting for orders from MainWindowViewModel. The main viewmodel is always in charge. It uses the model (that's DBHandler) to store and retrieve information, which it exposes in its properties. It also exposes commands. The view is how the user observes the the viewmodel and talks to it. The viewmodel doesn't know the view exists. All it knows is that somebody out in the darkness occasionally calls the getters and setters on its properties, or calls Execute on one of its commands.
Give MainWindowViewModel a public Names property that's an ObservableCollection<String>. Bind that to the DataGrid or whatever in the UI. Never use List<T> for anything in the viewmodel if there is the slightest chance that you'll be adding or removing items in it.
Write a new Command class called DelegateCommand, something like this:
public class DelegateCommand<T> : ICommand
{
public DelegateCommand(Action<T> action, Func<T, bool> canExecute = null)
{
_action = action;
_canExecute = canExecute;
}
private Action<T> _action;
private Func<T, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public bool CanExecute(object parameter)
{
if (_canExecute != null)
{
return _canExecute((T)parameter);
}
return true;
}
public void Execute(object parameter)
{
if (_action != null)
{
_action((T)parameter);
}
}
}
Use it:
public MainWindowViewModel()
{
// Initialize the command to add a name
_addNameCommand = new DelegateCommand<string>(DoAddName);
// Assign database collection entries
Names = new ObservableCollection<string>(DBHandler.GetInstance().Names);
}
public void DoAddName(String name)
{
Names.Add(name);
/*
Update database here
*/
}
ICommand _addName;
// Don't name anything "button" in your viewmodel; it's a bad habit to think
// that way. It's just a command. If the view wants to use a button to invoke
// it, that's the view's business. The viewmodel just makes stuff available.
public ICommand AddNameCommand
{
get {
Debug.WriteLine("getting AddNameCommand");
return _addNameCommand;
}
}
// Never, never, NEVER NEVER NEVER NEVER touch _names other than in the
// get and set blocks of Names.
// And make the set private. It should be kept in sync with the database, so
// don't let any other class but this one mess with it.
private ObservableCollection<string> _names = new ObservableCollection<string>();
public ObservableCollection<string> Names
{
get { return _names; }
private set {
if (_names != value)
{
_names = value;
NotifyPropertyChanged();
}
}
}
I don't know what you're doing to bind Names to the DataGrid, but I infer that it's working OK.
use ICollectionView - bind your Datagrid to it - and call .Refresh() in your Viewmodel whenever you wanna refresh your Datagrid
Related
I have a ViewModel that is a DependencyObject for which the DependencyPropertys are not updating the View with the new values.
A sample property (the get/set wrapper is called as expected)
public static readonly DependencyProperty WeaponNameProperty = DependencyProperty.Register(
"WeaponName",
typeof(string),
typeof(WeaponSystemVM),
new PropertyMetadata(null, new PropertyChangedCallback(OnWeaponNameChanged)));
public string WeaponName
{
get { return (string)GetValue(WeaponNameProperty); }
set { SetValue(WeaponNameProperty, value); }
}
The Callback (called when WeaponName is changed)
private static void OnWeaponNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WeaponSystemVM vm = d as WeaponSystemVM;
if (vm != null)
{ vm.CommandAddWeaponSystem.RaiseCanExecuteChanged(); }
}
The CanExecute Delegate (gets run as expected and updates the relevant Button)
private bool CanAddSystem()
{
if (string.IsNullOrWhiteSpace(WeaponName)) return false;
if (string.IsNullOrWhiteSpace(WeaponLock)) return false;
if (string.IsNullOrWhiteSpace(WeaponDamage)) return false;
if (string.IsNullOrWhiteSpace(WeaponAttack)) return false;
return true;
}
The input TextBox
<TextBox x:Name="NameInput" Text="{Binding WeaponName, Mode=TwoWay}" Margin="12,4" RelativePanel.Below="NameAdorner" RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"/>
The output TextBlock (is NOT updated with the new value and the DataContext is the same as the input TextBox)
<TextBlock Text="{Binding WeaponName}"/>
Frustratingly, it seems to be just this implementation that isn't working.
In an attempt to reproduce the issue, I created a seperate project without all the extra info associated with my app, and the View is being updated exactly as expected.
What I don't understand is what is not being done correctly in this implementation. The ViewModel is updating exactly as expected. The Bindings are valid according to the LiveVisualTree.
Can anyone point me to the issue?
You shouldn't use DependencyPropertys in your ViewModel: it is a markup class, used for binding on the View side. Overkill and out of scope for it being used that way.
You should implement INotifyPropertyChanged, and fire the INotifyPropertyChanged.PropertyChanged event in every single property you want to notify the UI about.
Something like:
your ViewModel inherits from
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetAndRaiseIfChanged<T>(
ref T backingField,
T newValue,
[CallerMemberName] string propertyName = null)
{
if (!object.Equals(backingField, newValue))
return;
backingField = newValue;
this.RaisePropertyChanged(propertyName);
}
}
and in your ViewModel you define your property like
private string _weaponName;
public string WeaponName
{
get { return this._weaponName; }
set { SetAndRaiseIfChanged(ref this._weaponName, value); }
}
a more concise CanAddSystem
private bool CanAddSystem()
{
return
!string.IsNullOrWhiteSpace(WeaponName)
&& !string.IsNullOrWhiteSpace(WeaponLock)
&& !string.IsNullOrWhiteSpace(WeaponDamage)
&& !string.IsNullOrWhiteSpace(WeaponAttack);
}
build your ViewModel's command with something that implements ICommand interface (something like a RelayCommand)
the View piece would be
<TextBlock Text="{Binding WeaponName}"/>
and you're done: when you bind an ICommand to the UI, the system automatically updates the CanExecute reading it from the ViewModel.
I am at my wits' end. Newbie to WPF so unsure where I am going wrong.
I have an MS Word Interop Add-in that captures selected text, along with various metadata. It composes my model and passes the data to it.
For this stage of development, I made my model a singleton just so that I know everything is pointing to the same reference.
Anyhow, my model is composed of a list of entries.
public sealed class MYMODELSingleton : ObservableObject, IMYMODEL
{
private static MYMODELSingleton instance;
private MYMODELSingleton()
{
isActive = true;
EntryList = new List<MyEntry.IMyEntry>();
}
public static MYMODELSingleton Instance
{
get
{
if(instance == null)
{
instance = new MYMODELSingleton();
}
return instance;
}
}
public bool isActive { get; set; }
private List<MyEntry.IMyEntry> _entryList;
public List<MyEntry.IMyEntry> EntryList
{
get { return _entryList; }
set { _entryList = value; OnPropertyChanged("EntryList"); }
}
public void Add(IMYEntry mEntry)
{
try {
EntryList.Add(mEntry);
OnPropertyChanged("EntryList");
}
catch(ArgumentException ae)
{
throw ae;
}
}
ObservableObject is a custom class I made that implements INotifyPropertyChanged
public abstract class ObservableObject : INotifyPropertyChanged
{
#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 OnPropertyChanged(string propertyName)
{
#if DEBUG
MessageBox.Show("Inside Event!");
#endif
var handler = this.PropertyChanged;
if (handler != null)
{
#if DEBUG
MessageBox.Show("Event Fired!");
#endif
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
}
This is my ViewModel
public class MyEntryViewModel : ObservableObject
{
private MYMODELSingleton activeMYMODEL= MYMODELSingleton.Instance;
public MyProjectModel.MYMODEL.MYMODELSingleton ActiveMYMODEL
{
get
{
return activeMYMODEL;
}
set
{
activeMYMODEL = value;
OnPropertyChanged("ActiveMYMODEL");
}
}
private void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "EntryList") {
int index = activeMYMODEL.EntryList.Count - 1;
MyEntry = activeMYMODEL.EntryList[index];
UpdateGui(MyEntry.Source);
}
}
public void UpdateGui(SelectionState selState)
{
TxtDocName = selState.SelectionDocName;
TxtDocPage = selState.SelectionPage;
TxtDocText = selState.SelectionText;
}
public MyEntryViewModel()
{
this.MyEntry = new MyEntry();
activeMYMODEL.PropertyChanged += new PropertyChangedEventHandler(ModelPropertyChanged);
//This is to notify me when the VM is created
#if DEBUG
System.Windows.Forms.MessageBox.Show("ViewModelOpened!");
#endif
}
~MyEntryViewModel()
{
activeMYMODEL.PropertyChanged -= new PropertyChangedEventHandler(this.ModelPropertyChanged);
#if DEBUG
System.Windows.Forms.MessageBox.Show("ViewModel Closed!");
#endif
}
}
}
Relevant WPF Code
xmlns:custns="clr-namespace:MyProjectViewModel.MyEntryViewModel;assembly=MyProjectViewModel"
Title="MyProject" Height="350" Width="525" SizeToContent="Height">
<Window.Resources>
<custns:MyyEntryViewModel x:Key="MyProjectObj" />
</Window.Resources>
<Grid Name="gridEntry" DataContext="{StaticResource MyProjectObj}" >
<TextBlock x:Name="docNameTxtBx" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="20" TextWrapping="Wrap" Text="{Binding TxtDocName, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
The problem is, while the entry gets added to mymodel's composed List no problem, the handler in OnPropertyChanged() always equals null. This, despite the fact that I know the ViewModel is instantiated (due to the messageboxes popping up).
FWIW, Model, ViewModel, WPF, and AddIn are all in different assemblies. The Viewmodel subscribes to the Model's event in its constructor, so why is my handler null when it's called???
UPDATE
After discussing this with a colleague, I realized the issue lies in how the MS Word ADDIN and the VM both compose the Model. Both are running in different threads and, apparently, both are holding separate instances of my Singleton! I am not sure how that is happening. I even changed my Singleton's code, making the private instance "volatile" and adding a static Object that I then lock when Instance.get() is called.
private static volatile MyModelSingleton instance;
private static object syncRoot = new Object();
private MyModelSingleton()
{
isActive = true;
EntryList = new List<MyEntry.IMyFEntry>();
}
public static MyModelSingleton Instance
{
get
{
lock (syncRoot)
{
if (instance == null)
{
instance = new MyModelSingleton();
}
}
return instance;
}
}
Nonetheless, both the Word Interop Addin and the Viewmodel are holding separate instances. What am I missing?
in the wpf code you presented, you never bind against ActiveMYMODEL. becuase it is never bound in you xaml, there is never a hook into you notify property changed event created.
I have observable collection called (Users) in view model that binded with ListViewControl (lstUsers) in view and what I need is to scroll to current logged in user in List View .
I see in most of examples that used scroll from code behind as following e.g. :
lstUsers.ScrollIntoView(lstUsers[5]);
but what I need is to handle it from view model .
Please advice !
One way of doing this would be to use something like an ICollectionView which has a current item. You can then set IsSynchronizedWithCurrentItem to true to link the current item in the view model to the selected item in the ListView.
Finally handle the event SelectionChanged in the code behind the view to change the scroll position so that it always displays the selected item.
For me the benefit of this method is that the viewmodel is kept unaware of anything about the view which is one of the aims of MVVM. The code behind the view is the perfect place for any code concerning the view only.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="View"
SelectionChanged="Selector_OnSelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Command="{Binding ChangeSelectionCommand}">Set</Button>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
View.ScrollIntoView(View.SelectedItem);
}
}
public class ViewModel
{
private readonly CollectionViewSource _source = new CollectionViewSource();
public ICollectionView Items
{
get { return _source.View; }
}
public ICommand ChangeSelectionCommand { get; set; }
public ViewModel()
{
SetUp();
ChangeSelectionCommand = new Command(ChangeSelection);
}
private void SetUp()
{
var list = new List<string>();
for (int i = 0; i < 100; i++)
{
list.Add(i.ToString(CultureInfo.InvariantCulture));
}
_source.Source = list;
}
private void ChangeSelection()
{
var random = new Random(DateTime.Now.Millisecond);
var n = random.Next(100);
Items.MoveCurrentToPosition(n);
}
}
public class Command : ICommand
{
private readonly Action _action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
_action = action;
}
}
let me share my solution with you
Create your own ListView descendant with dependency property TargetListItem
public class ScrollableListView : ListView
{
/// <summary>
/// Set this property to make ListView scroll to it
/// </summary>
public object TargetListItem
{
get { return (object)GetValue(TargetListItemProperty); }
set { SetValue(TargetListItemProperty, value); }
}
public static readonly DependencyProperty TargetListItemProperty = DependencyProperty.Register(
nameof(TargetListItem), typeof(object), typeof(ScrollableListView), new PropertyMetadata(null, TargetListItemPropertyChangedCallback));
static void TargetListItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var owner = (ScrollableListView)d;
owner.ScrollToItem(e.NewValue);
}
public void ScrollToItem(object value)
{
if (value != null && Items != null && Items.Contains(value))
{
ScrollIntoView(value);
}
}
}
create property in ViewModel
object currentListItem;
public object СurrentListItem
{
get => сurrentListItem;
set
{
if (сurrentListItem != value)
{
сurrentListItem = value;
OnPropertyChanged(nameof(СurrentListItem));
}
}
}
bind it
<controls:ScrollableListView ... TargetListItem="{Binding CurrentListItem}"/>
Now you can set CurrentListItem in ViewModel when needed. And the corresponding visual element will become visible in the ListView immediately.
Also maybe you just can use attached property on ListView instead of creating ScrollableListView. But i'm not sure.
Yep, there's always times in MVVM when you need to get at the control. There's various ways of doing this, but here's an easy-ish way of doing it without deriving from the control or messing with routed commands or other such toys what you have in WPF.
In summary:
Create an attached property on your view model.
Set the attached property in XAML to pass the list box back to the view model.
Call .ScrollIntoView on demand.
Note, this is a rough and ready example, make sure your DataContext is set before showing the window.
Code/View Model:
public class ViewModel
{
private ListBox _listBox;
private void ReceiveListBox(ListBox listBox)
{
_listBox = listBox;
}
public static readonly DependencyProperty ListBoxHookProperty = DependencyProperty.RegisterAttached(
"ListBoxHook", typeof (ListBox), typeof (ViewModel), new PropertyMetadata(default(ListBox), ListBoxHookPropertyChangedCallback));
private static void ListBoxHookPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listBox = (ListBox) dependencyObject;
var viewModel = (ViewModel) listBox.DataContext;
viewModel.ReceiveListBox(listBox);
}
public static void SetListBoxHook(DependencyObject element, ListBox value)
{
element.SetValue(ListBoxHookProperty, value);
}
public static ListBox GetListBoxHook(DependencyObject element)
{
return (ListBox) element.GetValue(ListBoxHookProperty);
}
}
OK, so that will let us get the ListBox passed back to the view; you can do with it as you wish.
Now, just set the property in XAML:
<ListBox wpfApplication1:ViewModel.ListBoxHook="{Binding RelativeSource={RelativeSource Self}}" />
Good to go!
Just started working with the MVVM design pattern and I'm stuck.
When my application launches, I have a treeview populated with a list of objects names. I've setup the IsChecked Binding, and it works fine. I'm trying to setup the IsEnabled Binding.
I want the user to select the items in the treeview he wants, then click one of three buttons to perform an action. On click, I want the selected items to remain in the treeview, but be disabled, so the user cannot perform another action on those items.
I'm using a RelayCommand class in the application.
private ICommandOnExecute _execute;
private ICommandOnCanExecute _canExecute;
public RelayCommand(ICommandOnExecute onExecuteMethod,
ICommandOnCanExecute onCanExecuteMethod)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
#endregion
My object model class uses this
private bool _isEnabled;
public bool IsEnabled
{
get { return true; }
set { _isEnabled = value};
}
Then within my button method I have
if (interfaceModel.IsChecked)
{
//Does Something
MyObjectName.IsEnabled = false;
}
And here is my xaml
<CheckBox IsChecked="{Binding IsChecked}" IsEnabled="{Binding IsEnabled, Mode=TwoWay}">
<TextBlock Text="{Binding MyObjectName}" Margin="5,2,1,2" HorizontalAlignment="Left" />
</CheckBox>
You need a setup like this:
// Your ViewModel should implement INotifyPropertyChanged
class ViewModel : INotifyPropertyChnaged
{
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
SetPropertyChanged("IsEnabled"); // Add this to your setter.
}
}
// This comes from INotifyPropertyChanged - the UI will listen to this event.
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged( this, new PropertyChangedEventArgs(property) );
}
}
}
Note that PropertyChanged comes from having your ViewModel implement INotifyPropertyChanged. To notify the UI, you have to raise that event, and tell it what property was changed (usually in the setter - see above).
Alternatively, if you don't like raw strings (I don't, personally), you can use generics and expression trees to do something like this:
public void SetPropertyChanged<T>(Expression<Func<T, Object>> onProperty)
{
if (PropertyChanged != null && onProperty.Body is MemberExpression)
{
String propertyNameAsString = ((MemberExpression)onProperty.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(propertyNameAsString));
}
}
Where in your setter you can say:
public bool IsEnabled
{
set
{
_isEnabled = value;
SetPropertyChanged<ViewModel>(x => x.IsEnabled);
}
}
And now it's strongly typed, which is kinda nice.
In my program I have tabItems that have their commands bound to a View Model. I am in the process of implementing a function that will copy the design structure of a "master" tabItem, along with it's command functionality in order to create a new tabItem. I need to do this because the user of this program will be allowed to add new tabItems.
Currently I am using the question Copying a TabItem with an MVVM structure, but I seem to be having trouble when the function tries to copy the Grid object using dependencyValue.
The class I am using:
public static class copyTabItems
{
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
}
When dependencyValue gets to {[Content, System.Windows.Controls.Grid]} the program throws an InvalidOperationException was Unhandled stating that, "Specified element is already the logical child of another element. Disconnect it first".
What does this mean? Is this a common problem with the Grid in WPF (am I breaking some rule by trying to do this?)? Is there something in my program that I am not aware of that is causing this?
Ok. This is how you're supposed to deal with a TabControl in WPF:
<Window x:Class="MiscSamples.MVVMTabControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MVVMTabControlSample" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Tab1ViewModel}">
<!-- Here I just put UI elements and DataBinding -->
<!-- You may want to encapsulate these into separate UserControls or something -->
<StackPanel>
<TextBlock Text="This is Tab1ViewModel!!"/>
<TextBlock Text="Text1:"/>
<TextBox Text="{Binding Text1}"/>
<TextBlock Text="Text2:"/>
<TextBox Text="{Binding Text2}"/>
<CheckBox IsChecked="{Binding MyBoolean}"/>
<Button Command="{Binding MyCommand}" Content="My Command!"/>
</StackPanel>
</DataTemplate>
<!-- Here you would add additional DataTemplates for each different Tab type (where UI and logic is different from Tab 1) -->
</Window.Resources>
<DockPanel>
<Button Command="{Binding AddNewTabCommand}" Content="AddNewTab"
DockPanel.Dock="Bottom"/>
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"
DisplayMemberPath="Title">
</TabControl>
</DockPanel>
</Window>
Code Behind:
public partial class MVVMTabControlSample : Window
{
public MVVMTabControlSample()
{
InitializeComponent();
DataContext = new MVVMTabControlViewModel();
}
}
Main ViewModel:
public class MVVMTabControlViewModel: PropertyChangedBase
{
public ObservableCollection<MVVMTabItemViewModel> Tabs { get; set; }
private MVVMTabItemViewModel _selectedTab;
public MVVMTabItemViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
OnPropertyChanged("SelectedTab");
}
}
public Command AddNewTabCommand { get; set; }
public MVVMTabControlViewModel()
{
Tabs = new ObservableCollection<MVVMTabItemViewModel>();
AddNewTabCommand = new Command(AddNewTab);
}
private void AddNewTab()
{
//Here I just create a new instance of TabViewModel
//If you want to copy the **Data** from a previous tab or something you need to
//copy the property values from the previously selected ViewModel or whatever.
var newtab = new Tab1ViewModel {Title = "Tab #" + (Tabs.Count + 1)};
Tabs.Add(newtab);
SelectedTab = newtab;
}
}
Abstract TabItem ViewModel (you to derive from this to create each different Tab "Widget")
public abstract class MVVMTabItemViewModel: PropertyChangedBase
{
public string Title { get; set; }
//Here you may want to add additional properties and logic common to ALL tab types.
}
TabItem 1 ViewModel:
public class Tab1ViewModel: MVVMTabItemViewModel
{
private string _text1;
private string _text2;
private bool _myBoolean;
public Tab1ViewModel()
{
MyCommand = new Command(MyMethod);
}
public string Text1
{
get { return _text1; }
set
{
_text1 = value;
OnPropertyChanged("Text1");
}
}
public bool MyBoolean
{
get { return _myBoolean; }
set
{
_myBoolean = value;
MyCommand.IsEnabled = !value;
}
}
public string Text2
{
get { return _text2; }
set
{
_text2 = value;
OnPropertyChanged("Text2");
}
}
public Command MyCommand { get; set; }
private void MyMethod()
{
Text1 = Text2;
}
}
Edit: I forgot to post the Command class (though you surely have your own)
public class Command : ICommand
{
public Action Action { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
And finally PropertyChangedBase (just a helper class)
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Basically, each Tab Item type is a Widget, which contains its own logic and Data.
You define all logic and data at the ViewModel or Model level, and never at the UI level.
You manipulate the data defined in either the ViewModel or the Model level, and have the UI updated via DataBinding, never touching the UI directly.
Notice How I'm leveraging DataTemplates in order to provide a specific UI for each Tab Item ViewModel class.
When copying a new Tab, you just create a new instance of the desired ViewModel, and add it to the ObservableCollection. WPF's DataBinding automatically updates the UI based on the Collection's change notification.
If you want to create additional tab types, just derive from MVVMTabItemViewModel and add your logic and data there. Then, you create a DataTemplate for that new ViewModel and WPF takes care of the rest.
You never, ever, ever manipulate UI elements in procedural code in WPF, unless there's a REAL reason to do so. You don't "uncheck" or "disable" UI Elements because UI elements MUST reflect the STATE of the data which is provided by the ViewModel. So a "Check/Uncheck" state or an "Enabled/Disabled" state is just a bool property in the ViewModel to which the UI binds.
Notice how this completely removes the need for horrendous winforms-like hacks and also removes the need for VisualTreeHelper.ComplicateMyCode() kind of things.
Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.