Changing values of DataContext in Command in WPF - c#

I want to change value of ViewModel property (which is binded with DataContext). Extremely easy with classic Events, with Commands it becomes formidable task. This is my code:
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
_vm.BtnClick = new BtnClick();
DataContext = _vm;
}
}
public class BtnClick : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Debug.WriteLine(parameter.ToString());
}
}
public class ViewModel
{
public ICommand BtnClick { get; set; }
public string Input { get; set; }
public string Output { get; set; }
}
<StackPanel>
<TextBox Text="{Binding Input}"></TextBox>
<TextBlock Text="{Binding Output}"></TextBlock>
<Button Command="{Binding Path=BtnClick}" CommandParameter="{Binding Input}">Translate</Button>
</StackPanel>
Command properly takes value from TextBox, now i want to do things with this value and save it to Output. And problem is from Command perspective i cannot access both DataContext and ViewModel.

The implementation of any command is usually in a viewmodel.
A framework or helper class is routinely used.
For example:
https://riptutorial.com/mvvm-light/example/32335/relaycommand
public class MyViewModel
{
.....
public ICommand MyCommand => new RelayCommand(
() =>
{
//execute action
Message = "clicked Button";
},
() =>
{
//return true if button should be enabled or not
return true;
}
);
Here, there is an anonymous method with that "clicked button" in it.
This will capture variables in the parent viewmodel.
You may therefore set a public property in the viewmodel that's bound to the text property in your view.
For the view to respond you will need to implement inotifypropertychanged and raise property changed in the setter of that public property.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification.
From the above.
If PersonName was bound to a textblock in the view.
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged();
}
}
In the command you can do:
PersonName = "Andy";
Which calls the setter of PersonName and a textblock bound to PersonName will read the new value.

Related

How to disable a button if textbox and passwordbox is blank in wpf?

I basically used a Model's (UserAccount) Property from my ViewModel(CreateAccountViewModel) to bind to my View, and call to my Command (CreateAccountCommand).
My Model(UserAccount):
public class UserAccount : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private string _username;
private string _password;
private DateTime _dateTime;
public int Id
{
get { return _id; }
set { _id = value; OnPropertyChanged(nameof(Id)); }
}
public string Username
{
get { return _username; }
set { _username = value; OnPropertyChanged(nameof(Username)); }
}
public string Password
{
get { return _password; }
set { _password = value; OnPropertyChanged(nameof(Password)); }
}
public DateTime DateCreated
{
get { return _dateTime; }
set { _dateTime = value; OnPropertyChanged(nameof(DateCreated)); }
}
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My ViewModel(CreateAccountViewModel):
public class CreateAccountViewModel: ViewModelBase
{
private UserAccount _userAccount;
public UserAccount CurrentUserAccount
{
get { return _userAccount; }
set { _userAccount = value; OnPropertyChanged(nameof(CurrentUserAccount)); }
}
public ICommand CreateAccountCommand{ get; }
public CreateAccountViewModel()
{
CreateAccountCommand= new CreateAccountCommand(this, Test);
CurrentUserAccount = new UserAccount();
}
public void Test()
{
MessageBox.Show("Random Message");
//I'm going to put my Create functionality here
}
}
My View (CreateAccountView):
<!--The TextBox for username-->
<TextBox Grid.Column="1"
Margin="10,0,0,0"
Text="{Binding Path=CurrentUserAccount.Username, UpdateSourceTrigger=PropertyChanged}" />
<!--The PasswordBox for password-->
<components:BindablePasswordBox Grid.Column="1"
Margin="10,0,0,0"
Password="{Binding Path=CurrentUserAccount.Password, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.ColumnSpan="2" />
<!--The Create user button-->
<Button Grid.Row="2"
Margin="0,20,0,0"
HorizontalAlignment="Center"
Command="{Binding CreateAccountCommand}"
Content="Create Account" />
My Command(CreateAccountCommand):
public class CreateAccountCommand: ICommand
{
private readonly CreateAccountViewModel _viewModel;
private readonly Action RunCommand;
public CreateAccountCommand(CreateAccountViewModel viewModel , Action runCommand)
{
_viewModel = viewModel;
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
RunCommand = runCommand;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
//This is supposed to check whether the Username textbox and Password passwordbox is blank (if both of them are blank, the button should be disabled, else disabled
return !string.IsNullOrEmpty(_viewModel.CurrentUserAccount.Username) && !string.IsNullOrEmpty(_viewModel.CurrentUserAccount.Password);
}
public void Execute(object parameter)
{
RunCommand();
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
My PasswordBox is bindable because I created a custom PasswordBox with DependencyProperty:
public partial class BindablePasswordBox : UserControl
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(string), typeof(BindablePasswordBox),
new PropertyMetadata(string.Empty));
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public BindablePasswordBox()
{
InitializeComponent();
}
//This method will notify us, whenever a password in our passwordBox changes
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
Password = passwordBox.Password; //sets the value of the DependencyProperty (PasswordProperty)
}
}
My problem here, is that, the button in my View does not change enable/disable even if I set my command's CanExecute to do so. Am I missing something obvious here? I really have to ask because I've been stuck here since yesterday. (My Main goal here is to disable the Create Account button if the Textbox and PasswordBox have no input. Any solutions are okay)
Lets do a small refactoring.
use CallerMemberNameAttribute (see here how) to have shorter property setters in vm;
write once reusable ICommand implementation and use it for all commands, see DelegateCommand;
rise command CanExecuteChanged in vm when you change one of command canExecuted condition;
UserAccount needs notifications (you have done it in the edit), if it's a model, then you need an extra vm to act as a wrapper, otherwise you wouldn't be able to catch changes done by the bound controls;
Since the properties of UserAccount are part of command canExecuted, you need to monitor for them.
With all changes your button using the command should be property enabled/disabled.
Below is pseudo-code (can contain mistakes):
public class CreateAccountViewModel: ViewModelBase
{
UserAccount _userAccount;
public UserAccount CurrentUserAccount
{
get => _userAccount;
set
{
// unsubscribe
if(_userAccount != null)
_userAccount -= UserAccount_PropertyChanged;
_userAccount = value;
// subscribe
if(_userAccount != null)
_userAccount += UserAccount_PropertyChanged;
// notifications
OnPropertyChanged(); // shorter syntax with CallerMemberNameAttribute
CreateAccountCommand.RaiseCanExecuteChanged();
}
}
public ICommand CreateAccountCommand { get; }
public CreateAccountViewModel()
{
CurrentUserAccount = new UserAccount();
CreateAccountCommand = new DelegateCommand(Test,
o => !string.IsNullOrEmpty(CurrentUserAccount.Username) && !string.IsNullOrEmpty(CurrentUserAccount.Password));
}
void Test(object parameter)
{
MessageBox.Show("Random Message");
//I'm going to put my Create functionality here
}
void UserAccount_PropertyChanged(object sender, NotifyPropertyChangedEventArgs e) =>
CreateAccountCommand.RaiseCanExecuteChanged(); // rise always of check for specific properties changes
}
The CreateAccountCommand hooks up en event handler to the view model's PropertyChanged but there is no such event raised when you set the Username and Password properties of the UserAccount object.
Either implement INotifyPropertyChanged in UserAccount or bind to wrapper properties of the CreateAccountViewModel:
public string Username
{
get { return _userAccount?.Username; }
set
{
if (_userAccount != null)
_userAccount.Username = value;
OnPropertyChanged();
}
}
If you decide to implement INotifyPropertyChanged in UserAccount, you still need to notify the command when the properties have been updated.
Since your CurrentUserAccount property may be set to a new value dynamically, you should remove and add the event handler dynamically:
private UserAccount _userAccount;
public UserAccount CurrentUserAccount
{
get { return _userAccount; }
set
{
if (_userAccount != null)
_userAccount.PropertyChanged -= OnUserAccountPropertyChanged;
_userAccount = value;
if (_userAccount != null)
_userAccount.PropertyChanged += OnUserAccountPropertyChanged;
OnPropertyChanged(nameof(CurrentUserAccount));
}
}
private void OnUserAccountPropertyChanged(object sender, PropertyChangedEventArgs e) =>
OnPropertyChanged(null);

UWP MVVM Data Binding for dummies (textbox.text from String)

Well, having a go at MVVM with UWP template 10. I have read many pages, and although everyone tries to say its really easy, I still can't make it work.
To put it into context, OCR is being run on an image, and I would like the text to be displayed in textbox automatically.
Here is my Model:
public class TextProcessing
{
private string _ocrText;
public string OcrText
{
get { return _ocrText; }
set
{
_ocrText = value;
}
}
}
Here is my ViewModel:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextProcessing _ocrTextVM;
public ScanPageViewModel()
{
_ocrTextVM = new TextProcessing();
}
public TextProcessing OcrTextVM
{
get { return _ocrTextVM; }
set {
_ocrTextVM = value;
this.OnPropertyChanged("OcrTextVM");
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Here is my View:
<TextBox x:Name="rtbOcr"
Text="{Binding OcrTextVM.OcrText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Firstly, that is not working. Could someone try to show where I am going wrong?
Then, the data is coming from a Services file, how would the Services update the value? What would be the correct code?
Thanks in advance.
Following code is cite from code.msdn (How to achieve MVVM design patterns in UWP), it will be helpful for you:
Check you code step by step.
1.ViewModel implemented interface INotifyPropertyChanged,and in property set method invoked PropertyChanged, like this:
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ProductName)));
}
}
}
}
2.Initialize you ViewMode in you page, and set DataContext as the ViewMode, like this:
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();
public MainPage()
{
...
this.DataContext = ViewModel;
}
}
3.In you xaml, binding data from viewMode, like this:
<TextBox Text="{Binding Path=ProductName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="ProductNameTextBox" TextChanged="ProductNameTextBox_TextChanged" />
Your OnPropertyChanged call on OcrTextVM isn't actually called in your case, since you set the value in the constructor to its backing field and bypass the property.
If you set the value via the property, it should work:
public ScanPageViewModel()
{
OcrTextVM = new TextProcessing();
}
Of course your view needs to know that ScanPageViewModel is its DataContext. Easiest way to do it is in the constructor of the code-behind of your view:
public OcrView()
{
DataContext = new ScanPageViewModel();
InitializeComponent();
}
Assuming your OCR service is returning a new TextProcessing object on usage, setting the property of OcrTextVM should suffice:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
//...
private void GetOcrFromService()
{
//...
TextProcessing value = OcrService.Get();
OcrTextVM = value;
}
}
On a note, the OcrTextVM name doesn't really reflect what the property is doing, since it doesn't look like it's a viewmodel. Consider renaming it.
Actually, it is very easy once I manage to understand. Here is the code needed to update a TextBox.Text
In the Models:
public class DisplayText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
In the XAML file:
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
In the ViewModels:
private DisplayText _helper = new DisplayText();
public DisplayText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
Then any mod from the ViewModels:
Helper.Text = "Whatever text, or method returning a string";

WPF Databind control in write-only mode

I'm trying to fiddle a little with WPF bindings, so I created a simple project.
Here's the code:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age {
get { return age; }
set {
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if(PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
My viewmodel contains ObservableCollection of Person, and single Person to track selected Person.
I've bound listbox's ItemsSource to ObservableCollection, and SelectedItem to single Person, called CurrentPerson. Also, I've bound TextBox to CurrentPerson.Name.
Code works fine, but whenever I change content of TextBox - my listbox also changes. And no matter what combination of "OneWay, TwoWay, OneWayToSource" binding modes on listbox\selecteditem I cannot prevent listbox from updating from CurrentPerson.
How can I prevent this behavior? I'd like to update listbox from CurrentPerson only by using ICommand interface from VM.
There is only one copy of the Person object which is being used in both ListBox.ItemsSource and TextBox.Text, so naturally updating that object from one location will reflect the change in the other as well.
Two easy solutions would be
Change the BindingMode on TextBox.Text to Explicit, so it doesn't update the Person object until you tell it to
Use a separate string property for TextBox.Text and copy it over to your SelectedPerson.Name whenever the command executes
Personally I prefer the second option because I'm not a big fan of bindings that don't accurately reflect the data object behind the UI component, and it would allow the user to change the SelectedItem without resetting the TextBox value.
For an example of the second option, your ViewModel might look like this :
public class MyViewModel()
{
ObservableCollection<Person> People { get; set; }
Person SelectedPerson { get; set; }
string NewPersonName { get; set; }
ICommand UpdatePersonName { get; }
}
where the UpdatePersonName command would execute
SelectedPerson.Name = NewPersonName;
and the CanExecute would only return true if
SelectedPerson != null
&& !NewPersonName.IsNullOrWhiteSpace()
&& NewPersonName != SelectedPerson.Name
I'm not sure if I've followed the question properly.
So, we have a class Person as
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age
{
get { return age; }
set
{
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
And we have a view model as
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Person> List { get; set; }
Person currentPerson;
public Person CurrentPerson {
get { return currentPerson; }
set { currentPerson = value;
FirePropertyChanged("CurrentPerson");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The xaml is
<ListBox ItemsSource="{Binding List}" SelectedItem="{Binding CurrentPerson}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" Width="100" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And I bind the view model to the view via
ViewModel vm = new ViewModel();
vm.List = new ObservableCollection<Person>();
foreach (var i in Enumerable.Range(1,10))
{
vm.List.Add(new Person() { Name = "Test" + i.ToString(), Age= i });
}
vm.CurrentPerson = null;
this.DataContext = vm;
Whenever I change the value at textbox, it updates the name properly. I tried to add a handler for list changed, but it doesn't happen to get triggered.
vm.List.CollectionChanged += List_CollectionChanged;
void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
MessageBox.Show(e.Action.ToString());
}
Can you comment if it isn't the same as your problem statement?
If you want to control when and what is saved/updated, you obviously need is a ViewModel for editing your Person model.
When selecting a person in your Listbox, you have to pass the person's id (avoid passing the object itself) to the PersonEditViewModel which is bound to the properties that shall be edited, load the persons data into the PersonEditViewModel and then edit. Once you hit the "Save" button, it should commit the change and update the database or whatever you are using for persistence.
Use either events/messages to pass values/events back and forth, or use a navigation approach (like INavigationAware interface in Prism).

Trouble copying a Grid object from one TabItem to another

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.

INotifyPropertyChanged.PropertyChanged fired, but UI not updated after form loads

Below is an example of my model , ViewModel and xaml binding. The viewmodel implements INotifyPropertChanged. The problem i'm having is...when the wpf form first loads i set ActiveStock and i see both setter and getter being called and the ui is updated to reflect the data correctly.
However, when i later set StockViewModel.ActiveStock, FirePropertyChanged is invoked but i don't see the getter being called, and consequently the UI does not update to reflect the new data. Any ideas what might be happening here?
The second question i have is whether i also need to raise PropertyChanged for the child properties (PriceData and CompanyData) of my model when ViewModel.ActiveStock is changed?
public class Stock
{
public string Ticker { get; set; }
public StockData PriceData { get; set; }
public StockData CompanyData { get; set; }
}
public class StockData
{
...
}
public class StockViewModel:INotifyPropertyChanged
{
private Stock _activeStock;
public Stock ActiveStock
{
get{ return _activeStock;}
set{ _activeStock = value; FirePropertyChanged("ActiveStock");}
}
...
}
XAML:
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding PriceData}" />
<UserControl Template="{StaticResource StockTemplate}" DataContext="{Binding ActiveStock}" Tag="{Binding CompanyData}" />
Edit:
if i remove the DataContext binding for the UserControl and instead set the DataContext for these two controls in code behind when ActiveStock changes, it works fine. why???
The getter is not being called because as far as I can see nothing is "getting" the value, The only properties used are PriceData and CompanyData and these don't use INotifyPropertyChanged
You will have to implement INotifyPropertyChanged on your Stock class for the UI to reflect the changes.
public class Stock : INotifyPropertyChanged
{
private string _ticker;
private StockData _priceData;
private StockData _companyData;
public string Ticker
{
get { return _ticker; }
set { _ticker = value; NotifyPropertyChanged("Ticker"); }
}
public StockData PriceData
{
get { return _priceData; }
set { _priceData = value; NotifyPropertyChanged("PriceData"); }
}
public StockData CompanyData
{
get { return _companyData; }
set { _companyData = value; NotifyPropertyChanged("CompanyData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You might want to try to specify the mode property on your datacontext bindings.
DataContext="{Binding ActiveStock, Mode=OneWay}"
I'm not sure that OneTime is the default binding for DataContext, but it would explain so if the above helps.
The second question has been answered by sa_ddam213.
HTH

Categories

Resources