MVVM Data Binding not updating - c#

I have a small WPF application and I'm beginning to learn the MVVM Data Binding pattern. I have an empty textBox and I have bound it to "FirstName" however when I run my code it's not updating. I have an ObservableObject class which inherits from the INotifyPropertyChanged class to check if the property has been updated. When I run the code the property does take the correct value however the UI never updates.
My code is as follows
MainWindow.xaml
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FirstName}" VerticalAlignment="Center" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="16" Margin="20,20,0,20" Width="132"></TextBox>
Main Windows.xaml.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
Person.cs
class Person:ObservableObject
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
}
MainViewModel.cs
class MainViewModel:ObservableObject
{
List<Person> pList = new List<Person>();
public MainViewModel()
{
pList = new List<Person>()
{
new Person() {FirstName="Craig"}
};
Init();
}
public void Init()
{
var li = pList.FirstOrDefault();
}
}
OnservableObject.cs
[Serializable]
public abstract class ObservableObject : INotifyPropertyChanged, IDisposable
{
#region Constructor
protected ObservableObject()
{
}
#endregion Constructor
#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 INotifyPropertyChanged Members
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
this.PropertyChanged?.Invoke(this, e);
}
protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpresssion)
{
var propertyName = PropertySupport.ExtractPropertyName(propertyExpresssion);
this.RaisePropertyChanged(propertyName);
}
protected void RaisePropertyChanged(String propertyName)
{
VerifyPropertyName(propertyName);
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged Members
#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
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 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()
{
}
/// <summary>
/// Useful for ensuring that ViewModel objects are properly garbage collected.
/// </summary>
~ObservableObject()
{
string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
Debug.WriteLine(msg);
}
#endregion IDisposable Members
}
PropertySupport.cs
public static class PropertySupport
{
public static String ExtractPropertyName<T>(Expression<Func<T>> propertyExpresssion)
{
if (propertyExpresssion == null)
{
throw new ArgumentNullException("propertyExpresssion");
}
var memberExpression = propertyExpresssion.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("The expression is not a member access expression.", "propertyExpresssion");
}
var property = memberExpression.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException("The member access expression does not access a property.", "propertyExpresssion");
}
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
{
throw new ArgumentException("The referenced property is a static property.", "propertyExpresssion");
}
return memberExpression.Member.Name;
}
}

You should make a property CurrentPerson, or something like this, in your MainViewModel. CurrentPerson should be e.g. the first entry of your pList. Then you can bind to CurrentPerson.FirstName.

The MainViewModel has no property called FirstName. In fact, it has no property at all.
If you define pList as a public property:
class MainViewModel : ObservableObject
{
public List<Person> pList { get; }
public MainViewModel()
{
pList = new List<Person>()
{
new Person() {FirstName="Craig"}
};
}
}
...you can bind to the first Person object in the List<Person> like this:
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding pList[0].FirstName}" />
Alternatively, you could add a Person property that returns the value of pList.FirstOrDefault() to the MainViewModel class and bind to this one in your XAML.
But you can only bind to public properties. You cannot bind to fields.

You are trying to bind the Model's property directly to the UI.
Think what you are going to bind to the TextBox. You are having a list of Person, from which you may bind selected person's FirstName property to the TextBox. In that sense, create a SelectedPerson property in ViewModel and bind the SelectedPerson.FirstName to the TextBox which would work.
Add the below property to your viewmodel
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson= value;
RaisePropertyChanged("SelectedPerson");
}
}
And try to bind as follows in XAML
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SelectedPerson.FirstName}" VerticalAlignment="Center" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="16" Margin="20,20,0,20" Width="132"></TextBox>

Related

Alter property setter logic programmatically

I need to add logic of a property setter.
For example, I have a property named "CurrentTab":
private WorkspaceViewModel _currentTab;
public WorkspaceViewModel CurrentTab
{
get
{
return _currentTab;
}
set
{
_currentTab = value;
OnPropertyChanged("CurrentTab");
}
}
This is all good and works, but I want to be able to just define it like this:
public WorkspaceViewModel CurrentTab { get; set; }
So that the system automatically performs the OnPropertyChanged() function for the property name after the setter has run without me adding any specific code.
How to identify which properties need to follow this logic is no problem, I just need to find a way how to actually do it.
I want to make this simpler because I'll be having quite a lot of those kind of properties and I'd like to keep it clean.
Is there a way?
Any help is much appreciated!
Take a look: Fody. There is an add-in for INotifyPropertyChange: github
It is manipulating IL code while building the solution.
You need only to add attribute to view model:
[ImplementPropertyChanged]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
}
When code gets compiled:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string givenNames;
public string GivenNames
{
get { return givenNames; }
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged("GivenNames");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenNames, FamilyName);
}
}
public virtual void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This can be achieved using PostSharp, which is an Aspect Oriented Programming approach:
In computing, aspect-oriented programming (AOP) is a programming
paradigm that aims to increase modularity by allowing the separation
of cross-cutting concerns. AOP forms a basis for aspect-oriented
software development.
You can implement this using an Aspect called InstanceLevelAspect:
/// <summary>
/// Aspect that, when apply on a class, fully implements the interface
/// <see cref="INotifyPropertyChanged"/> into that class, and overrides all properties to
/// that they raise the event <see cref="INotifyPropertyChanged.PropertyChanged"/>.
/// </summary>
[Serializable]
[IntroduceInterface(typeof(INotifyPropertyChanged),
OverrideAction = InterfaceOverrideAction.Ignore)]
[MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Property,
Inheritance = MulticastInheritance.Strict)]
public sealed class NotifyPropertyChangedAttribute : InstanceLevelAspect,
INotifyPropertyChanged
{
/// <summary>
/// Field bound at runtime to a delegate of the method OnPropertyChanged
/// </summary>
[ImportMember("OnPropertyChanged", IsRequired = false)]
public Action<string> OnPropertyChangedMethod;
/// <summary>
/// Method introduced in the target type (unless it is already present);
/// raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
[IntroduceMember(Visibility = Visibility.Family, IsVirtual = true,
OverrideAction = MemberOverrideAction.Ignore)]
public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this.Instance,
new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Event introduced in the target type (unless it is already present);
/// raised whenever a property has changed.
/// </summary>
[IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Method intercepting any call to a property setter.
/// </summary>
/// <param name="args">Aspect arguments.</param>
[OnLocationSetValueAdvice,
MulticastPointcut( Targets = MulticastTargets.Property,
Attributes = MulticastAttributes.Instance)]
public void OnPropertySet(LocationInterceptionArgs args)
{
// Don't go further if the new value is equal to the old one.
// (Possibly use object.Equals here).
if (args.Value == args.GetCurrentValue())
{
return;
}
// Actually sets the value.
args.ProceedSetValue();
// Invoke method OnPropertyChanged (our, the base one, or the overridden one).
this.OnPropertyChangedMethod.Invoke(args.Location.Name);
}
}
Then, use it on your property like this:
[NotifyPropertyChanged]
public WorkspaceViewModel CurrentTab { get; set; }
This attirubte can also be applied at the class level, if you want all your properties to implement NotifyPropertyChanged. More on the example can be found here

UI does not update using binding XAML

I have a problem when UI does not update to changing in variables, that are binded to control properties.
Help me understand why.
1) I have a class which inherited from UserControl and from InotifyPropertyChanged
public class BindableControl:UserControl, INotifyPropertyChanged
{
#region Data
private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache;
private const string ERROR_MSG = "{0} is not a public property of {1}";
#endregion // Data
#region Constructors
static BindableControl()
{
eventArgCache = new Dictionary<string, PropertyChangedEventArgs>();
}
protected BindableControl()
{
}
#endregion // Constructors
#region Public Members
/// <summary>
/// Raised when a public property of this object is set.
/// </summary>
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Returns an instance of PropertyChangedEventArgs for
/// the specified property name.
/// </summary>
/// <param name="propertyName">
/// The name of the property to create event args for.
/// </param>
public static PropertyChangedEventArgs
GetPropertyChangedEventArgs(string propertyName)
{
if (String.IsNullOrEmpty(propertyName))
throw new ArgumentException(
"propertyName cannot be null or empty.");
PropertyChangedEventArgs args;
// Get the event args from the cache, creating them
// and adding to the cache if necessary.
lock (typeof(BindableObject))
{
bool isCached = eventArgCache.ContainsKey(propertyName);
if (!isCached)
{
eventArgCache.Add(
propertyName,
new PropertyChangedEventArgs(propertyName));
}
args = eventArgCache[propertyName];
}
return args;
}
#endregion // Public Members
#region Protected Members
/// <summary>
/// Derived classes can override this method to
/// execute logic after a property is set. The
/// base implementation does nothing.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected virtual void AfterPropertyChanged(string propertyName)
{
}
/// <summary>
/// Attempts to raise the PropertyChanged event, and
/// invokes the virtual AfterPropertyChanged method,
/// regardless of whether the event was raised or not.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
this.VerifyProperty(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
// Get the cached event args.
PropertyChangedEventArgs args =
GetPropertyChangedEventArgs(propertyName);
// Raise the PropertyChanged event.
handler(this, args);
}
this.AfterPropertyChanged(propertyName);
}
#endregion
#region Private Helpers
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
Type type = this.GetType();
// Look for a public property with the specified name.
PropertyInfo propInfo = type.GetProperty(propertyName);
if (propInfo == null)
{
// The property could not be found,
// so alert the developer of the problem.
string msg = string.Format(
ERROR_MSG,
propertyName,
type.FullName);
Debug.Fail(msg);
}
}
#endregion
}
2) Then I have another classes, each of them inherited from BindableControl like this
public class CameraLocalization : BindableControl
{
public CameraLocalization()
{
headers = new CameraHeaders();
toolTips = new CameraToolTips();
SetRuLocal();
//SetEnLocal();
}
private Language lang = SettingsManager.Language.ru_RU;
private CameraHeaders headers;
private CameraToolTips toolTips;
public Language Lang
{
get { return lang; }
set
{
lang = value;
SetLocal();
RaisePropertyChanged();
}
}
3) In XAML I link this class as usercontrol and do binding like this:
xmlns:language ="clr-namespace:SettingsManager.Localization.Camera"
<Grid>
<language:CameraLocalization x:Name="Localization"></language:CameraLocalization>
<GroupBox Header="{Binding ElementName=Localization, Path=Headers.PositionGroupHeader, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
4) from another page I try to change language:
xmlns:language ="clr-namespace:SettingsManager.Localization.Camera"
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Width="Auto" Margin="0,0,0,5">
<language:CameraLocalization x:Name="Localization"></language:CameraLocalization>
<ComboBox ItemsSource="{Binding Source={StaticResource Language}}" Width="70" HorizontalAlignment="Left"
SelectedValue="{Binding ElementName=Localization, Path=Lang, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
And nothing happens. In debug mode i see that value of propertis changing, but they doesnot update on UI. What is the problem here? Who knows?
Your properties should NOT be declared in the UI elements (UserControl), and these should NOT implement INotifyPropertyChanged. You must separate UI from data/logic by using the MVVM Pattern.
You should create a proper ViewModel and put your properties and property change notification there.

Binded model to View but button click on working

All,
Binded complete Model, values are getting displayed in controls but not able to get button click working... any suggestion? what I m missing or doing wrong ? Thanks
<Window x:Class="test" Title="test" Height="350" Width="525">
<StackPanel Name="abc" Orientation="Vertical" DataContext="{Binding Path=EMP, Mode=TwoWay}" Margin="4" Height="153">
<Label Content="Last Name:" Margin="0,0,4,0"/>
<TextBox Width="250" Text="{Binding Path=LastName}" Height="20"/>
<Button Grid.Row="2" Margin="0,0,4,0" Height="40" Width="40"
Command="{Binding Path=SaveCommand}" />
</StackPanel>
</Window>
class EmployeeVM: ViewModelBase
{
private bool _Execute = true;
public EmployeeVM()
{
emp = new Model.Employee { FirstName = "abc", LastName = "xyz" };
}
private string sFirstName;
private string sLastName;
private Model.Employee emp;
public Model.Employee EMP
{
get{return emp;}
set{emp = value;
OnPropertyChanged("EMP");}
}
public string LastName
{
get { return sLastName; }
set
{
sLastName = value;
OnPropertyChanged("LastName");
}
}
#region Commands
private ICommand _SaveCommand;
public ICommand SaveCommand
{
get
{
return _SaveCommand = new CommandHandler(Save, _Execute);
}
}
#endregion
private void Save(object param)
{
ObservableCollection<Model.Employee> newIM = new ObservableCollection<Model.Employee>();
foreach(Model.Employee e in newIM)
{
string a = e.FirstName;
string b = e.LastName;
}
}
}
public class CommandHandler : ICommand
{
Action<object> _act;
bool _canExecute;
public CommandHandler(Action<object> act, bool canExecute)
{
_act = act;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_act(parameter);
}
}
You can write your own command.
Here is the baseclass I use for my commands.
It has some very basic things that make life easier.
the Execute method accepts an object, so you will be able to pass arrays
a viewmodel can be easily passed in, that is the one you will work with in your commands (this is most of the time the case, swipe it out if you do not need that)
the changed handler leverages the CommandManager. This is really very helpful
Perhaps you want to change some things. All I have added is in there because it is very helpful. (especially the viewmodel)
public abstract class CommandBase : ICommand
{
public abstract bool CanExecute(object o);
public abstract void Execute(object o);
public PropertyChangedBase ViewModel { get; set; }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
You would have implementations like
public class ExampleCommand : CommandBase
{
public ExampleCommand (PropertyChangedBase viewModel)
{
this.ViewModel = viewModel;
}
public override void Execute(object o)
{
// something like
var settings = UnityContainer.Resolve<ISettings>();
settings.MagicValue = (this.ViewModel as ConcreteViewModel).MagicValue;
}
public override bool CanExecute(object o)
{
return true;
}
}
in your ViewModel, you expose the command to the view by having a property:
public class ExampleViewModel : PropertyChangedBase
{
public ExampleViewModel ()
{
this.DoThisAndThatCommand = new ExampleCommand(this);
}
public CommandBase DoThisAndThatCommand { get; set; }
}
// and in XAML, you can use it like
<Button x:Name="Ok"
Command="{Binding DoThisAndThatCommand }" />
(Given you have connected the ViewModel and the View correctly by setting the DataContext of the View)
Now, whenever the Button is clicked, the Execute Method of the Command will get called.
You have your ViewModel right in the Command, so you can easily work with it.
It is very unusual to have a button inside a command or inside a ViewModel.
The trick about MVVM is to separate the View from the ViewModel and to not have
UIElements in the ViewModel.
If you do not have the PropertyChangedBase (this one comes with Caliburn.Micro) then I would suggest to use some easy INotifyPropertyChanged implementation.
I found this one here, should be german though
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
#region < INotifyPropertyChanged > Members
/// <summary>
/// Is connected to a method which handle changes to a property (located in the WPF Data Binding Engine)
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the [PropertyChanged] event
/// </summary>
/// <param name="propertyName">The name of the property</param>
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private Dictionary<string, object> propertyValueStorage;
#region Constructor
public NotifyPropertyChangedBase()
{
this.propertyValueStorage = new Dictionary<string, object>();
}
#endregion
/// <summary>
/// Set the value of the property and raise the [PropertyChanged] event
/// (only if the saved value and the new value are not equal)
/// </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="property">The property as a lambda expression</param>
/// <param name="value">The new value of the property</param>
protected void SetValue<T>(Expression<Func<T>> property, T value)
{
LambdaExpression lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
}
string propertyName = this.getPropertyName(lambdaExpression);
T storedValue = this.getValue<T>(propertyName);
if (!object.Equals(storedValue, value))
{
this.propertyValueStorage[propertyName] = value;
this.OnPropertyChanged(propertyName);
}
}
/// <summary> Get the value of the property </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="property">The property as a lambda expression</param>
/// <returns>The value of the given property (or the default value)</returns>
protected T GetValue<T>(Expression<Func<T>> property)
{
LambdaExpression lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
}
string propertyName = this.getPropertyName(lambdaExpression);
return getValue<T>(propertyName);
}
/// <summary>
/// Try to get the value from the internal dictionary of the given property name
/// </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="propertyName">The name of the property</param>
/// <returns>Retrieve the value from the internal dictionary</returns>
private T getValue<T>(string propertyName)
{
object value;
if (propertyValueStorage.TryGetValue(propertyName, out value))
{
return (T)value;
}
else
{
return default(T);
}
}
/// <summary>
/// Extract the property name from a lambda expression
/// </summary>
/// <param name="lambdaExpression">The lambda expression with the property</param>
/// <returns>The extracted property name</returns>
private string getPropertyName(LambdaExpression lambdaExpression)
{
MemberExpression memberExpression;
if (lambdaExpression.Body is UnaryExpression)
{
var unaryExpression = lambdaExpression.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambdaExpression.Body as MemberExpression;
}
return memberExpression.Member.Name;
}
}
It is very easy to use!
In your ViewModel you have to provide public properties for Binding (this is VERY important) and fire a change notification.
Here is an example of how to use that basic implementation of INPC (INotifyPropertyChanged)
public class LoginViewModel : NotifyPropertyChangedBase
{
public string UserName { get;set; }
}
This INPC implementation makes the NotifyOfPropertyChange call for you, you do not have to care for it! But you will have to inspect what fits your case best.
In your Question you already have a ViewModelBase. Perhaps you want to use this one instead of the above.
Please try to write in English, because I was confused what you wrote(such as "If u c above", "b/c", and so on :P..)
Anyway, as for your problem, this should fix it:
<UserControl.Resources>
<C:MultiValueConverter x:Key="MultiParamConverter"></C:MultiValueConverter>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Name="Expander" Content="+" Width="25" Margin="4,0,4,0" Command="{Binding ExpanderCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiParamConverter}">
<Binding ElementName="Content"/>
<Binding ElementName="Expander"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<Label FontWeight="Bold">GENERAL INFORMATION</Label>
</StackPanel>
<StackPanel Name="Content" Orientation="Vertical" Visibility="Collapsed">
<Label>Test</Label>
</StackPanel>
</StackPanel>
Command:
public ICommand ExpanderCommand
{
get
{
return new RelayCommand(delegate(object param)
{
var args = (object[])param;
var content = (UIElement)args[0];
var button = (Button)args[1];
content.Visibility = (content.Visibility == Visibility.Visible) ? Visibility.Collapsed : Visibility.Visible;
button.Content = (content.Visibility == Visibility.Visible) ? "-" : "+";
});
}
}
and the converter:
public class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values.ToArray();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("No two way conversion, one way binding only.");
}
}
First of all, your view model class should either be a DependencyObject, or implementing INotifyPropertyChanged interface. You can always find a suitable MVVM library out there and use their base view model class.
Judging from your XAML that your CheckBox can bind to the same context of your button. So, while you are binding your buttonGetDetails into the ClickCommand you can also bind your chkDuplicates into a view model property, lets say CheckDuplicates. Therefore you won't need this as a parameter to your command, because the property will already be inside your view model. Such as:
class TestViewModel : ViewModelBase
{
bool checkDuplicates;
public bool CheckDuplicates
{
get { return checkDuplicates; }
set
{
if(checkDuplicates != value)
{
checkDuplicates = value;
OnPropertyChanged("CheckDuplicates");
}
}
}
//Everything else is same as before
// except the action
public void AnyAction(object param)
{
//no need for param anymore
//var parmValues = (Object)param;
bool test = this.CheckDuplicates;
}
}
Since this is supposed to model your view, you can get rid of any parameters of your command binding and make them part of your view model.

Binding ObservableCollection contained in ViewModel to ListView

I've searched and searched but can't obtain a proper, helpful answer.
I have a MainWindow wpf window. Its DataContext is set to its ViewModel.
I have a ListView which is binded to an ObservableCollection in the ViewModel:
<ListView Grid.Row="1" Grid.Column="0" Margin="2" Name="sources_ListView" Grid.RowSpan="1" ItemsSource="{Binding Path=Sources}">
<ListView.View>
<GridView>
<GridViewColumn Width="290" Header="Name"
DisplayMemberBinding="{Binding Path=OriginalPath}"/>
<GridViewColumn Width="80" Header="Type"
DisplayMemberBinding="{Binding Path=Type}"/>
</GridView>
</ListView.View>
</ListView>
RelayCommand:
public ICommand BrowseFileFolderCommand
{
get
{
if (_browseFileFolderCommand == null)
{
_browseFileFolderCommand = new RelayCommand(o =>
{
_sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
}, null);
}
return _browseFileFolderCommand;
}
}
Now obviously what the Lambda function does, wouldn't work in the real world as I have taken it out of context, but accept the fact that it does add SourceItem to the ObservableCollection _sources and that there is a Public Sources which gets the _sources. I have also made the type that ObservableCollection takes use INotifyChangedProperty.
When I use that RelayCommand which is inside a button which adds a source to the ObservableCollection, the ListView doesn't update?
Thanks for any help
EDIT SourceItem:
public class SourceItem : ISourceItem, INotifyPropertyChanged
{
DirectoryInfo _sourceFolder;
public DirectoryInfo SourceFolder { get { return _sourceFolder; } private set { _sourceFolder = value; } }
FileInfo _sourceFile;
public FileInfo SourceFiles { get { return _sourceFile; } private set { _sourceFile = value; } }
string _originalPath;
public string OriginalPath { get { return _originalPath; } private set { _originalPath = value; OnPropertyChanged("OriginalPath"); } }
bool _isFolder;
public bool IsFolder { get { return _isFolder; } }
// display friendly property of IsFolder
public string Type { get { return _isFolder == true ? "Folder" : "File"; } }
public SourceItem(string originalPath, DirectoryInfo sourceFolder)
{
_originalPath = originalPath;
_sourceFolder = sourceFolder;
_sourceFile = null;
_isFolder = true;
}
public SourceItem(string originalPath, FileInfo sourceFile)
{
_originalPath = originalPath;
_sourceFile = sourceFile;
_sourceFolder = null;
_isFolder = false;
}
#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)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#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 = String.Format("Invalid property name: {0}", propertyName);
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
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
}
Use the public version of the property to add the new item
Sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
You are currently adding the item to the private version of your property (_sources), while your UI is bound to the public version of the property (Sources), so your UI does not get the CollectionChanged notification the private version of the property raises, so does not know it needs to update.
The alternative is to simply raise the PropertyChanged event for your class manually to tell the UI to update. This is usually what I do when I want to add a lot of items to my collection at the same time, but only have the UI update once.
_sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
RaisePropertyChanged("Sources");

Why is PropertyChangedEventHandler null?

The mainpage:
MainPage.xaml
<Canvas x:Name="LayoutRoot" Background="White">
</Canvas>
MainPage.xaml.cs
List<Usol> list = new List<Usol>();
for (int i = 0; i < 10; i++)
{
var element = new Usol();
list.Add(element);
Canvas.SetTop(element, i * 25);
LayoutRoot.Children.Add(list[i]);
}
foreach (var item in list)
{
item.context.name = "Varken";
}
A usercontrol
Usol.xaml
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Name}" />
</Grid>
Usol.xaml.cs
public Context context;
public Usol()
{
InitializeComponent();
context = new Context();
this.DataContext = context;
}
A class
Context.cs
public class Context : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region Fields
/// <summary>
/// Field Declaration for the <see cref="Name"/>
/// </summary>
private string name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the Name
/// </summary>
public string Name
{
get { return name; }
set
{
if (this.name != value)
{
this.name = value;
OnPropertyChanged("Name");
}
}
}
#endregion
}
Situation
I have created this small test application to copy a problem I have in a bigger application. It works about the same way (not exactly, but close enough).
It adds several custom made usercontrols and each get a own instance of a datacontext class.
However, none of the properties are willing to update themselfs due to a empty PropertyChangedEventHandler.
Question
Why is public event PropertyChangedEventHandler PropertyChanged; always null?
Context.cs needs to implement INotifyPropertyChanged interface. Are you doing that?
Edit: Post your update.
I have generally seen this kind of problem when programmers create "two" instances of Model/ViewModel. While you attach one instance with View, it's always the other one that gets update (which ofcourse will have a null PropertyChanged subscribers). Thus, you must make sure that your view is using the same instance as being updated at other parts.
Hope my point is clear.
Your code is wrong,
OnPropertyChanged("Name"); <-- should update "name" not "Name"
You are firing event saying that "Name" is changed, but name of property is "name", C# and binding are case sensitive.
Change it to,
#region Fields
/// <summary>
/// Field Declaration for the <see cref="name"/>
/// </summary>
private string _Name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the name
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (this._Name != value)
{
this._Name = value;
OnPropertyChanged("Name");
}
}
}
#endregion
From C# 6 on wards, please use nameof() keyword...
#region Fields
/// <summary>
/// Field Declaration for the <see cref="name"/>
/// </summary>
private string _Name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the name
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (this._Name != value)
{
this._Name = value;
OnPropertyChanged(nameof(Name));
}
}
}
#endregion

Categories

Resources