WPF MVVM Editable Combobox new value is null - c#

Tried all the solutions for similar issues around here, still no go. I have a ComboBox that should work for selection of existing items and/or for adding new ones. Only the selected item part works. Category is just an object with a Name and Id.
Thanks in advance!
XAML
<ComboBox Name="CbCategory" ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory.Name, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding NewCategory.Name}" DisplayMemberPath="Name"
IsEditable="True"/>
Code behind
private Category _selectedCategory;
public Category SelectedCategory
{
get { return _selectedCategory; }
set
{
if (Equals(_selectedCategory, value)) return;
_selectedCategory = value;
SendPropertyChanged("SelectedCategory");
}
}
private Category _newCategory;
public Category NewCategory
{
get { return _newCategory; }
set
{
if (Equals(_newCategory, value)) return;
_newCategory = value;
SendPropertyChanged("NewCategory");
}
}

Your Text Binding doesn't work because you're binding against a null Category property. Instantiate it instead.
public Category NewCategory
{
get { return _newCategory ?? (_newCategory = new Category()); }
set
{
if (Equals(_newCategory, value)) return;
_newCategory = value;
SendPropertyChanged("NewCategory");
}
}
Edit: Elaborating as per your comment:
Your ComboBox.Text binding is set to "{Binding NewCategory.Name}", so no matter what the value of SelectedCategory is, the Text property will always reflect the name of the NewCategory.
When NewCategory is null, the Text property has nothing to bind to, and therefore the 2-way binding cannot be executed (that is, the value of the Text property cannot be passed back to NewCategory.Name, because that will cause an NullReferenceException (because the NewCategory is null).
This does not affect the case of the SelectedItem, because that's binding directly to the SelectedCategory property, and not a sub-property of that.

Create new varible to keep text of combobox. If the selectedItem having null value get the text of combobox as new Item,
Code :
<ComboBox Name="CbCategory" ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory.Name, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding Name}" DisplayMemberPath="Name"
IsEditable="True"/>
private String _name;
public Category Name
{
get { return _name; }
set
{
_name = value
SendPropertyChanged("Name");
}
}
public ICommand ItemChange
{
get
{
`return new RelayCommand(() =>`{
try{string item = this.SelectedCategory.Code;}
catch(Exception ex){string item = this.Name;}
}, () => { return true; });
}
}

Related

Set selected value to 0 when passed selected value is not available in binded itemsource list

I wanted to set selected value to 0 when passed selected value is not available in binded itemsource list.
There is a class called Category as mentioned below:
public class Category
{
private long _CategoryID;
public long CategoryID
{
get
{
return _CategoryID;
}
set
{
_CategoryID = value;
}
}
private string _CategoryName;
public string CategoryName
{
get
{
return _CategoryName;
}
set
{
_CategoryName = value;
}
}
}
There is a window and a combobox inside that window. And combobox is defined as below:
<ComboBox Name="cbCagtegory" TabIndex="7" ItemsSource="{Binding CategoriesList}" SelectedValuePath="CategoryID" DisplayMemberPath="CategoryName" SelectedValue="{Binding Path=CategoryID}">
</ComboBox>
In the back end of this window I have prepared a list of categories to bind with combobox and a property to set combobox selected value.
List<Category> CategoriesList= new List<Category>();
private long _CategoryID;
public long CategoryID
{
get
{
return _CategoryID;
}
set
{
_CategoryID = value;
RaisePropertyChanged("CategoryID");
}
}
Now let say, CategoriesList contains two records.
a. CategoryID=0, CategoryName='Category1'
b. CategoryID=1, CategoryName='Category2'
c. CategoryID=2, CategoryName='Category3'
Question.
if I set CategoryId property value to 5. There will be nothing selected in the combobox.
At this time i wanted to set CategoryID=0 as selected value.
And I wanted to achieve this in XAML.
What I have tried so far.
1.Assigned FallBackValue=0 as below
<ComboBox Name="cbCagtegory" TabIndex="7" ItemsSource="{Binding CategoriesList}" SelectedValuePath="CategoryID" DisplayMemberPath="CategoryName" SelectedValue="{Binding Path=CategoryID,FallBackValue=0}">
</ComboBox>
2. Assigned TargetNullValue=0 as below
<ComboBox Name="cbCagtegory" TabIndex="7" ItemsSource="{Binding CategoriesList}" SelectedValuePath="CategoryID" DisplayMemberPath="CategoryName" SelectedValue="{Binding Path=CategoryID,TargetNullValue=0}">
</ComboBox>
Result
None of them worked. nothing got selected in combobox.
i would do this:
change the combobox in xaml in this way
<ComboBox Name="cbCagtegory" TabIndex="7" ItemsSource="{Binding CategoriesList}" DisplayMemberPath="CategoryName" SelectedItem="{Binding Selection}">
</ComboBox>
add this property
private Category comboSelection;
public Category ComboSelection
{
get
{
return ComboSelection;
}
set
{
ComboSelection= value;
RaisePropertyChanged("ComboSelection");
}
}
Change CategoryID like this
private long _CategoryID;
public long CategoryID
{
get
{
return _CategoryID;
}
set
{
_CategoryID = value;
if((int)CategoryID > CategoriesList.Count -1)
{ComboSelection= CategoriesList[0];}
else
{ComboSelection= CategoriesList[(int)CategoryID ];
_CategoryID = 0;}
RaisePropertyChanged("CategoryID");
}
}

MVVM Editable ComboBox Bindings

Here's my issue...I have a list of servers, each with an ID and ServerName. I want to be able to select a server from the ComboBox and edit it in place, then have its ID available to update via SQL later. So let's say this is the data: (ID=1, Name="Server1"), (ID=2, Name="Server2"), (ID=3, Name="Server3"). If I select Server3 from the ComboBox, I'd like to edit it to be "Server4" then upload that with a SQL query (I know how to do this part). I'm utilizing MVVM, so all the values are properties of my ViewModel.
Currently, when the text field is modified in the ComboBox the SelectedServer immediately becomes null, presumably because it is no longer a value it recognizes. I could use some guidance on how to get this to do what I'm trying to do.
<ComboBox Grid.Column="1" x:Name="serverNameUpdateBox" HorizontalAlignment="Stretch" Height="23" VerticalAlignment="Center" IsEditable="True"
ItemsSource="{Binding Path=DataContext.SelectedProjectServers, ElementName=main}"
DisplayMemberPath="ServerName"
SelectedValue="{Binding SelectedServer}"
SelectedValuePath="ServerName"
Text="{Binding SelectedServer.ServerName, UpdateSourceTrigger=LostFocus}"
/>
And ViewModel relevant code:
namespace ViewModel
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
SelectedProjectServers = new List<Server>();
SelectedServer = new Server();
private Server _selectedServer;
public Server SelectedServer
{
get { return _selectedServer; }
set
{
if (value == null) { ModifiedServer = _selectedServer; }
_selectedServer = value;
RaisePropertyChanged("SelectedServer");
}
}
private List<Server> _selectedProjectServers;
public List<Server> SelectedProjectServers
{
get { return _selectedProjectServers; }
set
{
_selectedProjectServers = value;
RaisePropertyChanged();
}
}
}
}
}
And Model relevant code:
namespace Model
{
public class Server : INotifyPropertyChanged
{
private string _serverName;
public string ServerName
{
get { return _serverName; }
set
{
_serverName = value;
RaisePropertyChanged();
}
}
private int _serverID;
public int ServerID
{
get { return _serverID; }
set
{
_serverID = value;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string caller = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
}
}
Bind a property like "EditedServerName" to Combobox.Text.
When the "EditedServerName" is changed you can set the value to the "ServerName" of your SelectedServer.
<ComboBox Grid.Column="1" x:Name= "serverNameUpdateBox" HorizontalAlignment= "Stretch" Height= "23" VerticalAlignment= "Center" IsEditable= "True"
ItemsSource= "{Binding Path=DataContext.SelectedProjectServers, ElementName=main}"
DisplayMemberPath= "ServerName"
SelectedItem="{Binding SelectedServer}"
Text= "{Binding EditedServerName, UpdateSourceTrigger=LostFocus}"
/>
ComboBox is primarily used for selection. You could have used another control like datagrid or so for update functionality.
Well, if you want to do it the ComboBox way, I suggest that you place a couple of text boxes below your combo box and bind these text boxes content to the SelectedServer properties i.e.
<TextBox x:name="ServerName" Text ={Binding SelectedServer.ServerName} />
and so on.
So, when ever a server is selected, these text boxes will be filled with values from currently selected Server. And then, you can trigger some command using a button below these boxes which triggers the sql query and passes the required data using bound properties from text boxes.
I hope you got the idea.

How to set a default selected item with a listview/combo box uwp

I would like a ComboBox to have a default selected value, in this case it would be the first item in the viewmodel.
<ComboBox Name="cat_choices" ItemsSource="{x:Bind ViewModel.Categories}" Width="300" VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="local:Category">
<TextBlock Text="{x:Bind cat_name}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
From the viewmodel:
private ObservableCollection<Category> categories = new ObservableCollection<Category>();
public ObservableCollection<Category> Categories { get { return this.categories; } }
Thank you.
Create 2 new property in your ViewModel:
private int _SelectedIndex = 0;
public int SelectedIndex
{
get
{
return _SelectedIndex;
}
set
{
_SelectedIndex = value;
RaisePropertyChanged(nameof(SelectedIndex));
}
}
private Category _SelectedQuality = null;
public Category SelectedQuality
{
get
{
return _SelectedQuality;
}
set
{
_SelectedCategory = value;
RaisePropertyChanged(nameof(SelectedCategory ));
}
}
When you finish populate your Categories, set SelectedCategory = Categories.First();
in xaml, binding SelectedIndex and SelectedItem property of ComboBox to the newly created properties in ViewModel, set binding mode = two way.
How about setting the SelectedIndex to 0 in the Loaded event in code behind of the ComboBox? Since you always want to set the selected item to the first item, this will be simple enough.
Or
If you want it to be implemented in XAML itself, use an EventTrigger for Loaded Event inside the ComboBox and use a Setter to set the property SelectedIndex to 0.

c# wpf Combox value in editable field must be different that displaymemberpath

I have a Combobox with a concate string in displaymemberpath ('DescriptionComplete' in code-behind below), and that is what i get in the editable field BUT this is only the IdEchantillon that i want into the editable field...
How must i do please ?
XAML:
<ComboBox x:Name="cbEchantillon" SelectedValue="{Binding CurrentEchantillon.IdEchantillon}" ItemsSource="{Binding OcEchantillon}" DisplayMemberPath="DescriptionComplete" SelectedValuePath="IdEchantillon" SelectedItem="{Binding CurrentEchantillon}" Text="{Binding IdEchantillon}" IsEditable="True" Width="355" FontSize="14" FontFamily="Courier New" SelectionChanged="cbEchantillon_SelectionChanged"></ComboBox>
Code behind:
public class Echantillon : ViewModelBase
{
private string _IdEchantillon;
private string _Description;
private DateTime _dateechan;
private string _descriptionComplete;
}
public string IdEchantillon
{
get { return _IdEchantillon; }
set { _IdEchantillon = value; RaisePropertyChanged("IdEchantillon"); }
}
public string Description
{
get { return _Description; }
set { _Description = value; RaisePropertyChanged("Description"); }
}
public DateTime Dateechan
{
get { return _dateechan; }
set { _dateechan = value; RaisePropertyChanged("Dateechan"); }
}
public string DescriptionComplete
{
get { return string.Format("{0} {1} {2}", IdEchantillon.PadRight(20), Dateechan.ToShortDateString(), Description); }
set { _descriptionComplete = value; }
}
}
At first, please, cleanup your ComboBox property bindings. You should not create binding for SelectedValue, because you already have binding for SelectedItem property. The SelectedValue is determined by extracting the value by SelectedValuePath from the SelectedItem.
In case of binding to ViewModel, you should not also bind Text property. Use ItemsSource as you do, and set DisplayMemberPath property to what you want to show as the text representation of the every item in the bound collection.

Property not bound correctly in ListBox DataTemplate

I've been having some trouble getting a listbox to correctly bind to a collection.
I'll give the framework code, then explain what I want it to do.
XAML Markup:
<ListBox DataContext="{Binding Foos, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding Main.SelectedFoo, Mode=TwoWay,
Source={StaticResource Locator},
UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding Main.SelectedFoo, Source={StaticResource Locator}}"/>
<ListBox ItemsSource="{Binding Main.SelectedFoo.Bars}" SelectedItem="{Binding Main.SelectedBar}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Right">
<!-- The binding requires "{Binding .}" because a path must be explicitly set for Two-Way binding,
even though {Binding .} is supposed to be identical to {Binding} -->
<TextBox Text="{Binding Path=. , UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
C# ViewModel:
private ObservableCollection<Foo> _barList = new ObservableCollection<Foo>();
private const string BardListPN = "FooList";
public ObservableCollection<Foo> FooList
{
get { return _fooList; }
set
{
if (_fooList == value)
{
return;
}
var oldValue = _fooList;
_fooList = value;
RaisePropertyChanged(FooListPN);
}
}
private Foo _selectedFoo;
private const string SelectedFooPN = "SelectedFoo";
public Foo SelectedFoo
{
get { return _selectedFoo; }
set
{
if (_selectedFoo == value)
{
return;
}
var oldValue = _selectedFoo;
_selectedFoo = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedFooPN);
}
}
public const string SelectedBarPN = "SelectedBar";
private string _selectedBar = "";
public string SelectedBar
{
get
{
return _selectedBar;
}
set
{
if (_selectedBar == value)
{
return;
}
var oldValue = _selectedBar;
_selectedBar = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedBarPN);
}
}
C# Model:
public class Foo
{
public ICollection<string> Bars
{
get { return _bars; }
set
{
_bars= value;
NotifyPropertyChanged("Bars");
// snipped obvious INotifyPropertyChanged boilerplate code
}
}
}
My problem is that any changes to the textboxes for the strings in the Bar collection aren't set. When the selected Foo changes to a different Foo and back, the original Bars are displayed.
Could someone tell me what I'm doing wrong? This seems like it should be much more simple. Thanks!
Update: I've changed the code as per Tri Q's suggestion, but the changes made to the textbox aren't reflected in the property itself. Any ideas?
Your Foo model class I take has been simplified for this example, but the omitted code could be the culprit of your problem. Let me explain.
Foo also needs to implement INotifyPropertyChanged to let the Listbox know when you have initialized the Bars collection and this most definitely depends on when you are initializing it.
Say you initialize Bars in Foo's constructor will cause the Listbox ItemsSource to bind to a valid Bars collection.
public Foo()
{
Bars = new ObservableCollection<string>();
...
}
Buut if you did something like this, the Listbox will not know that the Bars collection has been initialized and will not update it's source...
public Foo SelectedFoo
{
get { return _selectedFoo; }
set
{
if (_selectedFoo == value)
{
return;
}
var oldValue = _selectedFoo;
_selectedFoo = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedFooPN);
if(_selectedFoo.Bars == null)
{
_selectedFoo.Bars = new ObservableCollection<string>();
// ...
}
}
}
Also here are a few things you might want to revise in your XAML.
Firstly, binding of the Textbox is TwoWay by default, so you do not need to set the Mode or the Path.
<TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged}" />
Secondly, it makes no sense to set Mode="TwoWay" for ItemsSource. ItemsSource="{Binding Main.SelectedFoo.Bars, Mode=TwoWay}"
Finally, you don't need to set the DataType for your DataTemplate. DataType="{x:Type System:String}"

Categories