Binding ComboBox MVVM - c#

This is the first time I try to bind combobox. I'm trying get values from my database. However, with the code below, I'm getting this as a result (the same number of results as the rows count of my table):
GUITest.DB.Structure
where GUITest -> namespace of my project, DB -> folder where structure.cs is.
private ObservableCollection<Structure> _lists;
public ObservableCollection<Structure> Lists
{
get { return _lists; }
set
{
_lists = value;
NotifyOfPropertyChange("Lists");
}
}
public ObservableCollection<Structure> GetStructures()
{
ObservableCollection<Structure> products = new ObservableCollection<Structure>();
using (SqlConnection conn =
new SqlConnection(ConfigurationManager.ConnectionStrings["StringConnexion"].ConnectionString))
{
conn.Open();
SqlCommand cmdNotes =
new SqlCommand("SELECT * FROM Structure", conn);
using (SqlDataReader reader = cmdNotes.ExecuteReader())
{
var ordinals = new
{
CodeStr = reader.GetOrdinal("CODE_STR"),
NomStr = reader.GetOrdinal("NOM_STR"),
};
while (reader.Read())
{
var temp = new TableStructure();
temp.CodeStr = reader.GetString(ordinals.CodeStr);
temp.NomStr = reader.GetString(ordinals.NomStr);
products.Add(temp.SqlProduct2Product());
}
}
}
return products;
}
public CreateAccountViewModel()
{
_lists = new ObservableCollection<Structure>();
Lists = GetStructures();
}
XAML:
<ComboBox SelectedItem="{Binding Path=NomStr}" ItemsSource="{Binding Lists}"></ComboBox>

As noted in the comments, you want DisplayMemberPath not SelectedItem
DisplayMemberPath says "Display this property (as a path) as the ItemTemplate" it is functionally equivalent (though not code equivalent) to, for a path of X:
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=X}"/>
</DataTemplate>
</ComboBox.ItemTemplate
</ComboBox>
This is why you don't have the Binding extension in it, the framework puts it in for you.
SelectedItem is just that, the current selection of the combo box. It doesn't affect the display in any way.

Related

XAML binding issues with connected comboboxes

I have two comboboxes: Categories and Types. When my form is initially displayed, I am listing all categories and types that exist in the database. For both comboboxes, I manually insert row 0 to be of value “All” so that they don’t have to choose either if they don’t want to.
I have both comboboxes bound to ReactiveObjects so that if the user selects a Category, the Types combobox is automatically re-populated with a query to show only types relevant to the selected Category along with the row 0 added.
When the user selects a Category, it runs the query properly, returns the relevant Types, adds the row 0 properly and the combobox is populated correctly; however, on the XAML size it’s not selecting the row 0, and it adds the red outline around the combobox signifying an invalid selection was made.
If no choice is made for the Type combobox and the form is submitted, the correct value of 0 is passed. So while everything is working properly, the red box around the Types combobox is communicating to the user that they did something wrong and I cannot determine why the XAML isn’t picking up the selected values. I have run the code without it adding the row 0 and it still has the same behavior, i.e., the combobox is populated correctly, but no row is selected and the red outline appears.
XAML for comboboxes
<ComboBox
Grid.Row="3"
Grid.Column="1"
Width="200"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Style="{StaticResource SimpleComboBox}"
ItemsSource="{Binding Categories}"
SelectedValue="{Binding SearchCriteria.CategoryID}"
SelectedValuePath="ComboValueID"
DisplayMemberPath="ComboDataValue"
/>
<TextBlock
Grid.Row="3"
Grid.Column="2"
Style="{StaticResource NormalTextNarrow}"
Text="Type" VerticalAlignment="Top"
/>
<ComboBox
Grid.Row="3"
Grid.Column="3"
Width="200"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Style="{StaticResource SimpleComboBox}"
ItemsSource="{Binding Types}"
SelectedValue="{Binding SearchCriteria.TypeId}"
SelectedValuePath="ComboValueID"
DisplayMemberPath="ComboDataValue"
/>
Relevant VM code
// Definition of SearchCriteria. ResourceItem is a ReactiveObject and
// all of the relevant properties watch for changes in values.
private ResourceItem searchCriteria;
public ResourceItem SearchCriteria
{
get { return searchCriteria; }
set { this.RaiseAndSetIfChanged(ref searchCriteria, value); }
}
// This all happens in my constructor
// Defining Row 0
var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All" };
// Populating the comboboxes from the database
Categories = omr.GetKTValues("RES_CATEGORIES");
Types = omr.GetKTValuesRU("RES_TYPES");
// Adding the row 0
Categories.Insert(0, b);
Types.Insert(0, b);
// The form is displayed correctly at this point with the row 0 selected
Problem Code
// When the user picks a category, this is the method that is invoked:
private void categoryChanged()
{
if (SearchCriteria.CategoryID != 0)
{
Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);
SearchCriteria.TypeId = 0;
}
}
// This runs correctly and returns the relevant Types
public List<GenericCombobox> GetCategoryTypes(int categoryId)
{
string sql = "res.usp_GetCategoryTypes";
var types = new List<GenericCombobox>();
SqlConnection sqlCn = DatabaseCommunication.OpenConnection();
using (SqlCommand cmd = new SqlCommand(sql, sqlCn))
{
// Omitting db stuff for brevity...
try
{
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
types.Add(new GenericCombobox
{
ComboValueID = (int)dr["TypeId"],
ComboDataValue = (string)dr["Type"],
IsSelected = false,
Description = (string)dr["Type"],
ComboDataCode = (string)dr["Type"]
});
}
// More db-stuff omitted
}
// Adding the row 0
var b = new GenericCombobox { ComboValueID = 0, ComboDataValue = "All", IsSelected = false, Description = "All", ComboDataCode = "All" };
types.Insert(0, b);
return types;
}
Update with Additional Code
// Object containing the TypeId property
public class ResourceItem : ReactiveObject, ISelectable
{
public int Id { get; set; }
public int? OriginalItemId { get; set; }
// ...many other properties...
private int typeId;
public int TypeId
{
get { return typeId; }
set { this.RaiseAndSetIfChanged(ref typeId, value); }
}
// ...and many more follow...
I've been able to reproduce the issue, and I've found a few silly things I can do that make it stop happening.
If I select an item in Types other than "All", then when I change the selection in Category, it selects "All" in Types.
It also works if, in categoryChanged(), I replace this line...
SearchCriteria.TypeId = 0;
with this one:
SearchCriteria = new ResourceItem() {
TypeId = 0,
CategoryID = SearchCriteria.CategoryID
};
If I have ResourceItem.TypeId.set raise PropertyChanged regardless of whether the value has changed or not, it again works correctly.
My hypothesis is that the SelectedItem in the Types combobox (which you aren't even using!) is not changing when the collection changes, because you're not telling it to update SelectedValue.
Setting SearchCriteria.TypeId = 0 is a no-op when SearchCriteria.TypeId is already equal to zero, because RaiseAndSetIfChanged() does just what the name says: It checks to see if the value really changed, and if it hasn't, it doesn't raise PropertyChanged.
SelectedValue happens by chance to be the same value as the new "All" item has, but the combobox doesn't care. It just knows that nobody told it to go find a new SelectedItem, and the old one it has is no good any more because it's not in ItemsSource.
So this works too:
private void categoryChanged()
{
if (SearchCriteria.CategoryID != 0)
{
Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);
SearchCriteria.SelectedType = Types.FirstOrDefault();
//SearchCriteria.TypeId = 0;
//SearchCriteria = new ResourceItem() { TypeId = 0, CategoryID = SearchCriteria.CategoryID };
}
}
XAML:
<ComboBox
Grid.Row="3"
Grid.Column="3"
Width="200"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Types}"
SelectedValue="{Binding SearchCriteria.TypeId}"
SelectedItem="{Binding SearchCriteria.SelectedType}"
SelectedValuePath="ComboValueID"
DisplayMemberPath="ComboDataValue"
/>
class ResourceItem
private GenericCombobox selectedType;
public GenericCombobox SelectedType
{
get { return selectedType; }
set { this.RaiseAndSetIfChanged(ref selectedType, value); }
}
I think your best bet is my option #2 above:
private void categoryChanged()
{
if (SearchCriteria.CategoryID != 0)
{
Types = rr.GetCategoryTypes(SearchCriteria.CategoryID);
SearchCriteria = new ResourceItem() {
TypeId = 0,
CategoryID = SearchCriteria.CategoryID
};
// Do you need to do this?
// SearchCriteria.PropertyChanged += SearchCriteria_PropertyChanged;
}
}
The potential problem here is that, in my testing code I called categoryChanged() from a PropertyChanged handler on SearchCriteria. If I create a new SearchCriteria, I need to make sure I handle that event on the new one.
Given that, maybe binding SelectedItem on the Types combobox is the best solution after all: It's the only one I can think of that doesn't require the viewmodel to do strange things to make up for misbehavior in the view that it really shouldn't be aware of.

ListBox filled with binding doesn't select item on click

I'm trying to use a ListBox to choose an entry and then display a picture belonging to this selected entry. But just at the beginning I got my first problem: filling the ListBox with binding is working, but if I click on one line in my running program, it doesn't select the line. I can just see the highlighted hover effect, but not select a line. Any ideas what my mistake could be?
This is my XAML:
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontSize="24"/>
And in MainWindow.xaml.cs I'm filling the ListBox with entries:
private void fillEntrySelectionListBox()
{
//Fill listBox with entries for active user
DataContext = this;
entryItems = new ObservableCollection<ComboBoxItem>();
foreach (HistoryEntry h in activeUser.History)
{
var cbItem = new ComboBoxItem();
cbItem.Content = h.toString();
entryItems.Add(cbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + activeUser.Id;
//show image matching the selected entry
if (activeUser.History != null)
{
int index = entrySelection.SelectedIndex;
if (index != -1 && index < activeUser.History.Count)
{
this.entryImage.Source = activeUser.History[index].Image;
}
}
}
So I can see my ListBox correctly filled, but not select anything - so I can't go on with loading the picture matching the selected entry.
I'm still quite new to programming, so any help would be great :)
EDIT: If someone takes a look at this thread later: here's the - quite obvious -solution
XAML now looks like this
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontFamily="Siemens sans" FontSize="24">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to fill it:
//Fill listbox with entries for selected user
DataContext = this;
entryItems = new ObservableCollection<DataItem>();
foreach (HistoryEntry h in selectedUser.History)
{
var lbItem = new DataItem(h.toString());
entryItems.Add(lbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + selectedUser.Id;
And new Class DataItem:
class DataItem
{
private String text;
public DataItem(String s)
{
text = s;
}
public String Text
{
get
{
return text;
}
}
}
You are filling it with ComboBoxItem, which is not relevant to the ListBox, and also wrong by definition.
You need to have the ObservableCollection filled with data items.
Meaning, make a class that contains the data you want to store, and the ListBox will generate a ListBoxItem automatically per data item.
http://www.wpf-tutorial.com/list-controls/listbox-control/

WPF MVVM Entity Framework - Datagrid data binding

I use EF 6.1.3 Datebase first; WPF App + MVVM Pattern + DataGrid control.
The database has a table: Task, InnerErrand, OutErrand.
Tables InnerErrand/OutErrand instructions are basically the same and differ only slightly. Table Task does not know anything about the Inner/Out, it does not have the keys of the Inner/Out. Such is the specificity of the essence. But the errands know what tasks they perform.
Database Tables (MS SQL
Question: Is required for each out errand to display the task number, and if there are instructions Inner, also display it.
There DataGrid with data binding to the outer errand (OutErrand)
<DataGrid x: Name = "dataGrid" AutoGenerateColumns = "False"
ItemsSource = "{Binding CV}"
SelectedItem = "{Binding SelectedErrand, Mode = TwoWay, UpdateSourceTrigger = PropertyChanged}"
>
<DataGrid.Columns>
<! - Here you can see the out errand O1, O2, O3, O4, etc. ->
<DataGridTextColumn Header = "out errand" Binding = "{Binding Number, Mode = TwoWay}" />
<! - Here you can see the task number 1, 2, 3, 4, etc. ->
<DataGridTextColumn Header = "task number" Binding = "{Binding Task.Number, Mode = TwoWay}" />
<! - There must be displayed inner errand П1, П2, П3, П4, etc. ->
<DataGridTextColumn Header = "inner errand" Binding = "{Binding Task.InnerErrand.Number, Mode = TwoWay}" />
</DataGrid.Columns>
</ DataGrid>
ViewModel:
class ErrandViewModel: ViewModelBase
{
public ErrandViewModel ()
{
dbContext = new MyContext ();
LoadErrands ();
_cvs = new CollectionViewSource ();
_cvs.Source = Errands; // To filter
}
private void LoadErrands ()
{
var query = dbContext.OutErrand.ToList ();
this.Errands = query;
}
public List <OutErrand> Errands
{
get
{
return _errands;
}
set
{
_errands = value;
NotifyPropertyChanged ();
}
}
public OutErrand SelectedErrand
{
get
{
return _selectedErrand;
}
set
{
_selectedErrand = value;
NotifyPropertyChanged ();
}
}
public ICollectionView CV
{
get
{
return _cvs.View;
}
set
{
_cv = value;
NotifyPropertyChanged ();
}
}
} // End of class
} // End namespace
The problem is that the inner errand is not displayed in DataGrid.
<DataGridTextColumn Header = "inner errand" Binding = "{Binding Task.InnerErrand.Number, Mode = TwoWay}" />
It does not work at all.
If change SelectedErrand:
public OutErrand SelectedErrand
{
get
{
return _selectedErrand;
}
set
{
_selectedErrand = value;
MessageBox.Show (_selectedErrand.Task.InnerErrand.Select (x => x.Number) .First () ToString ().);
NotifyPropertyChanged ();
}
}
then the data is retrieved to MsgBox.
Tell me how to change the binding in the DataGrid, so that worked :)
Thanx for all :)

How to get selected text from WPF Combobox?

I am trying to get text from WPF ComboBox, when it will select but unable to get any text. Below are the codes which I have tried. If someone please help me to get text from ComboBox when I select different Content. please note that, ComboBox will load data from sql server which is working perfectly !
my XAML code is:
<ComboBox x:Name="comboID" TabIndex="27" SelectedValuePath="Content" SelectionChanged="comboID_SelectionChanged" HorizontalAlignment="Left" Margin="694,396,0,0" VerticalAlignment="Top" Width="165"/>
below are my C# codes example which I have tried:
string str1 = comboID.SelectedItem.ToString();
string str2 = comboID.SelectedValue.ToString();
string str3 = comboID.Text;
ComboBoxItem cmb = comboID.SelectedItem as ComboBoxItem;
var myVal = sender as ComboBox;
string value = comboID.SelectedItem as string;
Use this code instead of yours for adding items in combobox:
SqlConnection conn = new SqlConnection("your connection");
SqlCommand cmnd = new SqlCommand();
SqlDataReader sdr = null;
conn.Open();
cmnd.Connection = conn;
String query = "Select ID from Seller ORDER BY ID";
cmnd.CommandText = query;
sdr = cmnd.ExecuteReader();
while (sdr.Read())
{
comboID.Items.Add(sdr.GetString(0));
}
Now you can use:
string str = comboExporterID.SelectedItem.ToString();
I have created a simple code to give you a rough idea.
My Xaml
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="350">
<StackPanel Orientation="Horizontal">
<Label Content="Select Item" x:Name="LblSelectNumber" MinWidth="50px" Margin="5"></Label>
<ComboBox x:Name="ComboId" DisplayMemberPath="Number"
SelectedValuePath="Content" SelectionChanged="ComboID_OnSelectionChanged" MinWidth="100"
HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" />
</StackPanel>
</Window>
My code behind
public partial class MainWindow : Window
{
private readonly List<MyItem> _items;
public MainWindow()
{
InitializeComponent();
_items = new List<MyItem>
{
new MyItem{Content = "Test1",Number = "One"},
new MyItem{Content = "Test2",Number = "Two"},
new MyItem{Content = "Test3",Number = "Three"}
};
ComboId.ItemsSource = _items;
}
private void ComboID_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show(ComboId.SelectedValue.ToString());
}
}
public class MyItem
{
public string Content { get; set; }
public string Number { get; set; }
}
Please observe the usage of DisplayMemberPath property in ComboBox
A simple suggestion. Please use MVVM pattern while creating your application as it will help you to maintain the solution in the long run. What ever I have done above can be achieved quite easily using MVVM pattern
Simple code. Try this, should work
string s = comboID.text;
Add the Tag property to the comboboxitems:
<Comboboxitem Tag="This Value"/>
Then
GetValue=ComboBoxName.SelectedItem.Tag.ToString()
GetValue will be "This Value" instead of the System.windows.combobox blah blah blah..

WPF combobox databinding both to custom objects and to datatable.showing System.Data.DataRowView in dropdown list items

I've posted a similar problem here and wasn't able to successfully implement the solution suggested to me as it wasn't working.I've found a way round and wanted to improve it by binding the combobox to a custom objects to enable data validation.here is the xaml for this one
<Window xmlns:data="clr-namespace:Myproject">
<Window.Resources>
<data:UserLogin x:Key="user"></data:UserLogin>
<DataTemplate x:Key="comboTemplate">
<TextBlock Text="{Binding Path=username}" />
</DataTemplate>
</Window.Resources>
<ComboBox Margin="18,121,24,0" Name="cmbEmail" Tag="email" TabIndex="1" ToolTip="enter the email you signed up with here" IsEditable="True" IsSynchronizedWithCurrentItem="True" ItemTemplate="{StaticResource comboTemplate}" ItemsSource="{Binding}" Height="23" VerticalAlignment="Top" Style="{DynamicResource cmbBoxerrors}">
<ComboBox.Text>
<Binding Path="Loginname" Source="{StaticResource user}" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule/>
</Binding.ValidationRules>
</Binding>
</ComboBox.Text>
</ComboBox>
</Window>
and the xaml.cs is
if (con != null)
{
if (con.State == ConnectionState.Closed)
con.Open();
SqlCeCommand cmdusers = new SqlCeCommand("select * from users order by id", con);
SqlCeDataAdapter da = new SqlCeDataAdapter(cmdusers);
userdt = new DataTable("users");
da.Fill(userdt);
cmbEmail.DataContext = userdt;
}
and the UserLogin Class is
class UserLogin :IDataErrorInfo
{
private string _loginname = "";
private string _password;
public string this[string columnName]
{
get
{
string result = null;
if(columnName == "Loginname")
{
if(string.IsNullOrEmpty(this._loginname))
{
result = "Login Name cannot be Empty";
}
}
if (columnName == "Loginname")
{
if(!Util.ValidateRegexPatern(Properties.Resources.emailRegex,this._loginname))
{
result = "MalFormed Email address. Please write a correct email addess";
}
}
return result;
}
}
public string Error
{
get { return null; }
}
public string Password
{
get { return _password; }
set { _password = value; }
}
public string Loginname
{
get { return _loginname; }
set { _loginname = value; }
}
}
the problem is when i use ItemTemplate the selected item shows System.Data.DataRowView but dropdown list items show correctly and when i swap the ItemTemplate with DisplayMemberPath it's the opposite behavior as in selected items is correct and dropdown list items show System.Data.DataRowView.using them both throws an exception as i can't use them both selected and dropdown list items show correctly.
I don't really know what i'm not doing correctly.Can anyone shed some light on this i'll be verry thankfull.Thanks for reading this
It goes like this: you set the data context of the ComboBox to an instance of type DataTable. Then you set the ItemsSource to {Binding} which means each item in the ComboBox will bind to a DataRow (which doesn't have either loginname, nor username as properties). And here the binding stops working. There's no implicit way to convert from a DataRow to a UserLogin.
You ca either implement a converter to do the conversion or convert the rows to UserLogin one by one and set the DataContext of the ComboBox to the list of UserLogin (or an ObservableCollection if you need more advanced functionality).
In either case drop the <ComboBox.Text> ... </ComboBox.Text> part.
Hope this helps you...

Categories