OneWay/TwoWay binding in XAML using Lambda expression and MVVM pattern - c#

I can not bind the Combobox.SelectedItemin XAML in my ViewModel using the MVVM pattern with lambdaexpressions.
In my MainWindow.xaml i have:
<ComboBox ItemsSource="{Binding Products}" IsEnabled="{Binding ProductsIsEnabled}" SelectedItem="{Binding SelectedProduct}" />
In my MainWindow.xaml.cs I have the DataContext correctly set (all other pieces of my code in xaml work just fine).
In my MainWindowViewModel.cs I have:
...
public string TitleText => Title.Text;
...
public ObservableCollection<object> Products => MyConverter.GetCollection(ProductList);
public bool ProductsIsEnabled => MyProduct.IsEnabled;
public object SelectedProduct => ProductList.SelectedItem; // --> this does not work
...
The error i receive when running is
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Private.CoreLib.dll
A TwoWay or OneWayToSource binding cannot work on the read-only property 'SelectedProduct' of type 'MyNamespace.MainWindowViewModel'.
All my objects implement INotifyPropertyChanged.
Does anybody know what am I missing or doing wrong here?
Thanks in advance!

public object SelectedProduct => ProductList.SelectedItem; is a short form for
public object SelectedProduct
{
get
{
return ProductList.SelectedItem;
}
}
That means a property with a lambda Expression is a property without a setter; it is a read-only property. SelectedItem of the combo box can be changed by the user, that means the user can change the value of the SelectedProduct property.
To fix it, you'll have either to at a setter to the property or you'll have to change the binding two one way:
<ComboBox ItemsSource="{Binding Products}" IsEnabled="{Binding ProductsIsEnabled}" SelectedItem="{Binding SelectedProduct, Mode=OneWay}" />
One-Way-Binding means that the property stays unchanged if the user changes it. Only if you change the property, the UI element will change.
Addendum: After I read your comment: Your property ProductList.SelectedItem can have a getter and a setter, but from that it does not mean that your property SelectedProduct will have a setter and a getter. Change it to
public object SelectedProduct
{
get
{
return ProductList.SelectedItem;
}
set
{
ProductList.SelectedItem = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedProduct)));
}
}
and it should work.

Related

Prevent DataGrid from updating selected item in MVVM

I have a Datagrid with a list binded to ItemsSource and the SelectedItem is binded a single object of this list. My ViewModel implements INotifyPropertyChanged.
The binding works fine, except when there's a variable (canSelectOtherObject = false) that prevents myObject of changing it's value. Even thought myObject doesn't modify it's value, the datagrid on the View selects other object. How can I prevent this?
View:
<DataGrid ItemsSource="{Binding MyObjectList}" SelectedItem="{Binding MyObjectSelected, Mode=TwoWay}">
ViewModel:
private ObservableCollection<MyObject> myObjectList;
private MyObject myObjectSelected;
private bool canSelectOtherObject;
public ObservableCollection<MyObject> MyObjectList
{
get { return myObjectList; }
set { myObjectList = value; }
}
public MyObject MyObjectSelected
{
get { return myObjectSelected; }
set
{
if(canSelectOtherObject)
{
myObjectSelected = value;
OnPropertyChanged("MyObjectSelected");
}
}
}
Thanks!
INotifyPropertyChanged is used to notify the UI to update bindings when the properties of an object change, I think you are describing a situation where the object itself changes.
Given your binding:
<DataGrid ItemsSource="{Binding MicrophoneList}" SelectedItem="{Binding MicrophoneSelected, Mode=TwoWay}">
It's the difference between updating one of the properties of the selected microphone (would require INotifyPropertyChanged), and changing SelectedItem to a different microphone altogether (binding updates whether you notify or not).

Unable to clear selection in a combobox

Simple enough requirement - trying to reset a WPF combobox on user press of a "clear" button. Everything else on the form clears as expected, with the exception of this ComboBox.
<ComboBox ItemsSource="{Binding Members}" DisplayMemberPath="MemberName" SelectedValue="{Binding RequestingMember, Mode=TwoWay}" SelectedValuePath="MemberID" IsEditable="{Binding FixedRequestingMember }"></ComboBox>
Here's the property it's bound to:
public int RequestingMember
{
get { return _requestingMember; }
set
{
if (_requestingMember != value)
{
_requestingMember = value;
}
}
}
And here's what I'm using to clear the box:
this.RequestingMember = -1;
Worth mentioning that there's nothing in the Members collection which corresponds to a key of -1. The value doesn't change from its selection when you press clear, anyway.
I've tried setting the int to 0 and also setting UpdateSourceTrigger=PropertyChanged in the XAML, to no avail. I'm loathe to change RequestingMemeber to a type of int? as it'll need fixes that cascade a long way into other parts of the application.
What am I doing wrong?
Please read the Use SelectedValue, SelectedValuePath, and SelectedItem page on MSDN for the full information on this, but in short, you will have more luck by data binding to the SelectedItem property, rather than using the SelectedValue and SelectedValuePath properties. Try adding a property of the same type as the items in the collection and data binding that to the SelectedItem property instead:
public Member SelectedMember // Implement the INotifyPropertyChanged Interface here!!
{
get { return selectedMember; }
set
{
if (selectedMember != value)
{
selectedMember = value;
NotifyPropertyChanged("SelectedMember");
}
}
}
You will also need to implement the INotifyPropertyChanged Interface in your class with the properties. Your XAML should now look like this:
<ComboBox ItemsSource="{Binding Members}" DisplayMemberPath="MemberName"
SelectedItem="{Binding SelectedMember, Mode=TwoWay}"
IsEditable="{Binding FixedRequestingMember }" />
Now all you need to do to clear the selection is this:
SelectedMember = null;

ListView Observable Collection Will Not Show Members

Cannot get my listview to display data.
XAML
<Grid>
<DockPanel>
<ListView Name="lstDetectedComputers"
MinWidth="200"
DockPanel.Dock="Left"
ItemsSource="{Binding ComputersList}" DisplayMemberPath="ComputerName">
</ListView>
<DataGrid x:Name="ViewNetworkCardInformation"
ItemsSource="{Binding NetworkCardInformation}"/>
</DockPanel>
</Grid>
Code:
private ObservableCollection<Object> _ComputersList;
public ObservableCollection<Object> ComputersList
{
get
{
return _ComputersList;
}
set
{
_ComputersList = value; NotifyPropertyChanged("ComputersList");
}
}
private DataTable _NetworkCardInformation;
public DataTable NetworkCardInformation
{
get
{
return _NetworkCardInformation;
}
set
{
_NetworkCardInformation = value; NotifyPropertyChanged("NetworkCardInformation");
}
}
Strange thing is that NetworkCardInformation shows in my datagrid so this indicates that the datacontext is working as expected.
now im under the impression with a ObservableCollection i do not need a INotifyPropertyChange, if this is wrong please advised.
i have also tried just ItemsSource="{Binding ComputersList}"
I have put a break point into the code to ensure that the observable collection has data, and it is there .
ComputersList Count = 2 System.Collections.ObjectModel.ObservableCollection
[0] {AdminUltimate.Model.NetworkModel.ComputerNode} object {AdminUltimate.Model.NetworkModel.ComputerNode}
ComputerName "ASUS-PC" string
Could someone please assist.
Thank you
You have set DisplayMemberPath as ComputerName but Object doesn't have any such property so it shows nothing on view.
This can be validated by removing DisplayMemberPath, you will see fully qualified class name of your object since ToString() gets called on your object if no ItemTemplate and DisplayMemberPath is set on ListBox.
So, solution would be to change ObservableCollection<Object> to type of more concrete object containing property ComputerName i.e. ObservableCollection<ComputerNode>.

Explicit binding to an object without using the current DataContext

Description:
I have some View having its DataContext is already set to some List.
I also have in there a ComboBox that should trigger a Visibility event to a StackPanel. It is done through a property "SelectedVisibility" that is implementing a INotifyPropertyChanged.
Issue:
The property "SelectedVisibility" isn't part of the DataContext but in a ViewModel class and I cannot find any way to explicitely bind my ViewModel to reach that property.
Question:
Would you know how I could explicitely define my VM to be the DataContext of the SelectedValue binding in my ComboBox?
Code details:
View XAML:
<ComboBox ItemsSource="{Binding Source={StaticResource VisibilityEnum}}" SelectedValue="{Binding Path=SelectedVisibility}"/>
<StackPanel Visibility="{Binding Path=SelectedVisibility,Converter={StaticResource SelectedValueToVisible}}">
View Code behind:
public Counterparties_UserInputs()
{
// Cannot bind this as already bound
// this.DataContext = _VM;
InitializeComponent();
}
View Model:
public event PropertyChangedEventHandler PropertyChanged;
public string SelectedVisibility
{
get
{
return _selectedVisibility;
}
set
{
_selectedVisibility= value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedVisibility"));
}
}
}
Thank you in advance!
You can add a new dependancy property to your view, bind your view model to this property and then use this property as DataContext for your StackPanel and ComboBox. For example ("this" is the name of your view, "AdditionalContext" is the dependancy property you declare to store your viewmodel):
<StackPanel DataContext="{Binding AdditionalContext, ElementName=this}" Visibility="{Binding Path=SelectedVisibility, Converter={StaticResource SelectedValueToVisible}}"/>
However you should not do it, since it is a violation of MVVM pattern. The whole point of viewmodel is that you use it as DataContext for your view. The correct approach to your issue is to move List declaration to your viewmodel.

WPF ComboBox Binding To Object On Initial Load

I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.

Categories

Resources