What can I do if I want to bind to some property which is already bound to something else?
In my case, I got a window which has a TextBox. The Text property of this TextBox is data bound to a combo box to it's selectedItem. My Window class got a public string property which I want to update with whatever value is in the TextBox so I wanted to data bind with the Text property but as I said, it's already bound.
How can I update my property with the text in TextBox? Must it use a routed event of TextChanged or can I do it via Xaml?
Also specifically with properties you define them yourself in your window.cs ... how can you bind them to the TextBox.Text? I tried doing it with the <Window> declaration, meaning <Window MyProperty={Binding ...} /> but the property is not recognized there. Why and how do I do it?
You could solve this easily using the MVVM pattern.
ViewModel:
public class ChooseCategoryViewModel : ViewModelBase
{
private string[] _categories =
{ "Fruit", "Meat", "Vegetable", "Cereal" };
public string[] Categories
{
get { return _categories; }
}
private string _selectedCategory;
public string SelectedCategory
{
get { return _selectedCategory; }
set
{
_selectedCategory = value;
OnPropertyChanged("SelectedCategory");
if (value != null && CategoryName != value)
CategoryName = value;
}
}
private string _categoryName;
public string CategoryName
{
get { return _categoryName; }
set
{
_categoryName = value;
OnPropertyChanged("CategoryName");
if (Categories.Contains(value))
{
SelectedCategory = value;
}
else
{
SelectedCategory = null;
}
}
}
}
XAML:
<ComboBox ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}" />
<TextBox Text="{Binding CategoryName}" />
Related
I have a combobox binded to a table called Tenderness through MVVM. I'm using Entity Framework.It displays all the records properly, but I need to add another functionality to it. Suppose the user types in text which is not contained inside the combobox's Itemssource, I want to be able to add it directly to the table and then update the Itemssource as well. Now I have been able to do this without MVVM, I would want to know, how to achieve it using MVVM.
Just do what you did previously in the LostFocus event handler in the setter of a source property that you bind to the Text property of the ComboBox.
View Model:
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>() { "a", "b", "c" };
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged(nameof(Text));
//add the missing value...
if (!Items.Contains(_text))
Items.Add(_text);
}
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged(nameof(SelectedItem));
}
}
View:
<ComboBox IsEditable="True" Text="{Binding Text, UpdateSourceTrigger=LostFocus}" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}" />
I'm no sure what the right way to bind the combobox control is to my view model.
I'm using a MVVM approach so in my viewmodel i'm loading all the CDType data and am binding their source in combination with the actual record properties model.CDType.id and model.CDType.name.
What happens is that when i change the text? - i keep my old id (from the load routine) and get a new text value from the combobox binding therefor always writing over the existing record instead of creating a new one.
How can i make my combobox set the id to 0 / undefined if the text isn't in the list? (manually? - kind of lame)
Anything will help - thanks!
TL;DR: Editable Combo-box not updating ID on text change.
Sample Xaml binding:
<ComboBox x:Name="ddlCDType"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
TextSearch.TextPath="Name"
ItemsSource="{Binding CDTypes}"
SelectedValue="{Binding Assignment.CDType.ID}"
Text="{Binding Assignment.CDType.Name,
UpdateSourceTrigger=PropertyChanged,
NotifyOnValidationError=True,
ValidatesOnDataErrors=True}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
IsEditable="True"
HorizontalAlignment="Left"
Margin="98,10,0,0"
VerticalAlignment="Top"
Width="136" />
as I can understand you need update the previous last selected value when the name of selected ComboBox item is changed manually and this name is not presented in the ComboBox ItemSource. Here is my suggestion, it is combination of your ComboBox control data binding and logic defined in the ViewModel.
Explanation of binding
Since the ComboBox ItemsSource is the collection of Cd (the collection of ComboModels), thus the TextSearch.TextPath binding should be defined as CDType.Name where the CdType is property defined in the ComboModel that describes the sub-model, and the Name is an actual search path which is describing the sub-model.
The ComboBox Text property is binded to AssignmentText property to trigger the updating logic when the combo is lost the focus (as defined in binding).
The ItemsSource is trivial as defined in your sample code.
Selected value will bring a whole model to update (in case we change the selected value name).
Since the ComboBox ItemsSource is the collection of Cd (let's call this ComboModel) the DisplayMemberPath binding should be defined as CDType.Name where the CdType is property defined in the ComboModel that describes the sub-model, and the Name is an actual search path which is describing the sub-model.
Xaml Code:
<Grid VerticalAlignment="Bottom">
<Grid.DataContext>
<comboBoxWhenNoAnySelectedHelpAttempt:ComboboxDataContext/>
</Grid.DataContext>
<StackPanel Orientation="Vertical">
<ComboBox x:Name="ddlCDType"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
TextSearch.TextPath="CDType.Name"
Text="{Binding AssignmentText,
UpdateSourceTrigger=LostFocus, Mode=TwoWay,
ValidatesOnDataErrors=True}"
ItemsSource="{Binding CDTypes}"
SelectedValue="{Binding Assignment}"
DisplayMemberPath="CDType.Name"
IsEditable="True"
HorizontalAlignment="Left"
Margin="98,10,0,0"
VerticalAlignment="Top"
Width="136" />
<!--added to see changes in updated combo box item-->
<TextBlock >
<Run Text="Name:"/>
<Run Text="{Binding Assignment.CDType.Name, UpdateSourceTrigger=PropertyChanged}"></Run>
<Run Text="Id:"/>
<Run Text="{Binding Assignment.CDType.ID, UpdateSourceTrigger=PropertyChanged}"></Run>
</TextBlock>
</StackPanel>
</Grid>
VM code
public class ComboboxDataContext:BaseObservableObject
{
private ObservableCollection<ComboModel> _cdTypes;
private ComboModel _assignment;
private string _assignmentText;
private string _lastAcceptedName;
public ComboboxDataContext()
{
CDTypes = new ObservableCollection<ComboModel>
{
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-1",
ID = "1",
},
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-2",
ID = "2",
}
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-3",
ID = "3",
},
},
new ComboModel
{
CDType = new ComboModelSubModel
{
Name = "Cd-4",
ID = "4",
}
}
};
Assignment = CDTypes.FirstOrDefault();
}
public ObservableCollection<ComboModel> CDTypes
{
get { return _cdTypes; }
set
{
_cdTypes = value;
OnPropertyChanged();
}
}
public ComboModel Assignment
{
get { return _assignment; }
set
{
if (value == null)
_lastAcceptedName = _assignment.CDType.Name;
_assignment = value;
OnPropertyChanged();
}
}
//on lost focus when edit is done will check and update the last edited value
public string AssignmentText
{
get { return _assignmentText; }
set
{
_assignmentText = value;
OnPropertyChanged();
UpDateSourceCollection(AssignmentText);
}
}
//will do the the update on combo lost focus to prevent the
//annessasary updates (each property change will make a lot of noice in combo)
private void UpDateSourceCollection(string assignmentText)
{
var existingModel = CDTypes.FirstOrDefault(model => model.CDType.Name == assignmentText);
if (existingModel != null) return;
if (_lastAcceptedName == null)
{
CDTypes.Add(new ComboModel{CDType = new ComboModelSubModel{ID = string.Empty, Name = assignmentText}});
}
else
{
var existingModelToEdit = CDTypes.FirstOrDefault(model => model.CDType.Name == _lastAcceptedName);
if(existingModelToEdit == null) return;
existingModelToEdit.CDType.Name = assignmentText;
existingModelToEdit.CDType.ID = string.Empty;
}
}
}
public class ComboModel:BaseObservableObject
{
private ComboModelSubModel _cdType;
public ComboModelSubModel CDType
{
get { return _cdType; }
set
{
_cdType = value;
OnPropertyChanged();
}
}
}
public class ComboModelSubModel:BaseObservableObject
{
private string _id;
private string _name;
public string ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
}
BaseObservableObject code
/// <summary>`enter code here`
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Regards.
Please look at the image below:
Only three items in the listbox are displayed in the above image but it can be any number of items depending on the user's choice.
Now, as you can see in the image above each item has two comboboxes. Now I want to have selectedItem or SelectedValue in my viewModel from which I should be able to get the user's selection. Now I don't know how to bind these comboboxes for getting the user's selection.
Suppose I have only one item instead of the list then I would declare a property of type int so that I can easily get the selectedValue but for the list I am very much confused. Can anybody point me to the right direction?
To start of, lets say the class you are going to be binding the combo box is
public class UnitSource :INotifyPropertyChanged
{
public IEnumerable Units
{
get { return new[] { "Test Unit", "Alternate Unit" }; }
}
string _selectedComboItem1;
public string SelectedComboItem1
{
get
{
return _selectedComboItem1;
}
set
{
if (_selectedComboItem1 == value)
return;
_selectedComboItem1 = value;
OnPropertyChanged();
}
}
string _selectedComboItem2;
public string SelectedComboItem2
{
get
{
return _selectedComboItem2;
}
set
{
if (_selectedComboItem2 == value)
return;
_selectedComboItem2 = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in your view model you will have an ObservableCollection of the UnitSource Like below
public ObservableCollection<UnitSource> MuchoUnitSources
{
get; set;
}
To get the selected ListBoxItem have this in your ViewModel
private UnitSource _selectedUnitSource;
public UnitSource SelectedUnitSource
{
get
{
return _selectedUnitSource;
}
set
{
if (_selectedUnitSource == value)
return;
_selectedUnitSource = value;
OnPropertyChanged();
}
}
Lets assume it is initialized like so
MuchoUnitSources = new ObservableCollection<UnitSource>(new []{ new UnitSource(),new UnitSource() });
The in your view your listbox should look like below
<ListBox Name ="TestList1" ItemsSource="{Binding MuchoUnitSources}" SelectedItem="{Binding SelectedUnitSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<ComboBox SelectedItem="{Binding SelectedComboItem1}" ItemsSource="{Binding Units}" />
<ComboBox SelectedItem="{Binding SelectedComboItem2}" ItemsSource="{Binding Units}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now whenever you select an item from any of the combobox they will update the objectbeing bound to.
I am not able to find out Why is combo box binding not working?
I have a view model which looks like (2 properties)
public ProcessMaintenanceDataObject CurrentProcess
{
get
{
return _CurrentProcess;
}
set
{
_CurrentProcess = value;
OnPropertyChanged("CurrentProcess");
}
}
public ObservableCollection<ProcessMaintenanceDataObject > Processes
{
get
{
return _Processes;
}
set
{
_Processes = value;
OnPropertyChanged("Processes");
}
}
public ObservableCollection<FolderInfo> Folders
{
get
{
return _folders;
}
set
{
_folders = value;
OnPropertyChanged("Folders");
}
}
The following is the ProcessMaintenanceDataObject definition
[DataMember]
public string ProcessName
{
get
{
return _ProcessName;
}
set
{
_ProcessName = value;
OnPropertyChanged("ProcessName");
}
}
[DataMember]
public string Id
{
get
{
return _Id;
}
set
{
_Id = value;
OnPropertyChanged("Id");
}
}
[DataMember]
public string FolderId
{
get
{
return _FolderId;
}
set
{
_FolderId = value;
OnPropertyChanged("FolderId");
}
}
[DataMember]
public FolderInfo Folder
{
get
{
return _Folder;
}
set
{
_Folder = value;
if (_Folder != null)
FolderId = _Folder.FolderId;
OnPropertyChanged("Folder");
}
}
The FolderInfo class has FolderName and FolderId Property.
I have a method in viewmodel which fills the Processes.
In my view I have structure something like, I have a treeview which will be bound to Processes and while selecting any of the item from the treeview, i need to allow user to edit that entity.
In the view the combo box binding is as:
<ComboBox ItemsSource="{Binding Path=Folders, Mode=OneWay}"
DisplayMemberPath="FolderName"
SelectedItem="{Binding Source={StaticResource viewModel}, Path=CurrentProcess.Folder, Mode=TwoWay}">
...
This binding doesn't work I mean when I select any object from the tree it fills other information like ProcesName in the textBox but it doesn't make the Folder object as the selected item in combobox, however the combo box will be filled.
Any suggestion.
Do refer this:
If you want to bind a ComboBox to your folders property in two way mode with edit support,
Then you should define data template for your combo box and then bind properties of FolderInfo class to those text boxes
Binding display member path will not solve your problem
I'd suggest you to change DisplayMemberPath with appropriate DataTemplate:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FolderName}">
</StackPanel>
<DataTemplate>
This will fix SelectedItem context.
Maybe just maybe, your Folderinfo class is not a notificationObject. If it is the case make sure it implements INotifyPropertyChange.
you have to use SelectedValuePath and SelectedValue instead of SelectedItem like below,
<ComboBox ItemsSource="{Binding Path=Folders, Mode=OneWay}"
DisplayMemberPath="FolderName"
SelectedValuePath="FolderId"
SelectedValue="{Binding Path=FolderId, Mode=TwoWay}">
SelectedItem binds the whole object whereas SelectedValue binds only particular properties of the object.
I want to execute some function when user selects a different value in the combo box.
I am using MVVM pattern .
Any Idea how I Data Bind in this case ?
bind to a property on SelectedItem.
in the property's setter, execute your function.
SelectedItem="{Binding Path=SomeProperty}"
You'd want to either bind to the SelectedValue, SelectedItem, or SelectedIndex depending on what you want to do:
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyObj> MyCollection { get; private set; }
private MyObj _theItem;
public MyObj TheItem
{
get { return _theItem; }
set
{
if (Equals(value, _theItem)) return;
_theItem= value;
//Or however else you implement this...
OnPropertyChanged("TheItem");
//Do something here.... OR
//This will trigger a PropertyChangedEvent if you're subscribed internally.
}
}
private string _theValue;
public string TheValue
{
get { return _theValue; }
set
{
if (Equals(value, _theValue)) return;
_theValue= value;
OnPropertyChanged("TheValue");
}
}
private int _theIndex;
public int TheIndex
{
get { return _theIndex; }
set
{
if (Equals(value, _theIndex)) return;
_theIndex = value;
OnPropertyChanged("TheIndex");
}
}
}
public class MyObj
{
public string PropA { get; set; }
}
<!-- Realistically, you'd only want to bind to one of those -->
<!-- Most likely the SelectedItem for the best usage -->
<ItemsControl ItemsSource="{Binding Path=MyCollection}"
SelectedItem="{Binding Path=TheItem}"
SelectedValue="{Binding Path=TheValue}"
SelectedIndex="{Binding Path=TheIndex}"
/>