I have a listbox that binds to and displays the Name elements from an XML file. When a listbox item is selected, I want to display the Price value associated with this item in a textblock. How do I retrieve the Price programmatically (meaning not in the xaml file but in code behind)? Thanks.
XML file has these nodes:
<Product>
<Name>Book</Name>
<Price>7</Price>
</Product>
I use Linq and do the select with an anonymous type. If the easiest way to access the field programmatically is through a named type, please show me how.
Here's how I bind in xaml (using a data template for each listbox item that contains):
<TextBlock Text = "{Binding Name}" />
Here's the code-behind function where I want to retrieve the Price:
private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// how do I get the value of Price of the selected item here?
}
Please note that I want to access Price in this function, NOT in xaml!
First of all you probably don't even need LINQ as you can do a lot of things with XmlDocuments including doing selection via XPath (also in Bindings).
Secondly converting anonymous types to named types is trivial, if you have
select new { Name = ..., Price = ... }
You just need a class with the respective properties
select new Product { Name = ..., Price = ... }
public class Product
{
public string Name { get; set; }
public string Price { get; set; } // Datatype is up to you...
}
Thirdly you can make do without named types using dynamic.
private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBox)sender;
// Named type:
Product item = (Product)listBox.SelectedItem;
// Anonymous type:
dynamic item = listBox.SelectedItem;
// <Do something with item.Price, may need to cast it when using dynamic>
// e.g. MessageBox.Show((string)item.Price);
}
You should be able to retrieve the selected item from the SelectionChangedEventArgs parameter. i.e.
var item = e.AddedItems.First();
Refer to this post - bind textblock to current listbox item in pure xaml, you can get the name both in xaml and code-behind using XmlDataProvider.
Related
I am working with WPF for the first time, so please bear with me.
I have a combobox, which is meant to generically display some lookup data. The models for the different types of lookups are exactly the same, just different data sources which are retrieved via a single method call passing different enumerations to control the returned data set. Fairly simple stuff.
public sealed class MyDataProvider
{
public enum Types
{
Green,
Blue,
Orange
}
private readonly ConcurrentDictionary<string, ObservableCollection<LookUpVm>> _lookupData =
new ConcurrentDictionary<string, ObservableCollection<LookUpVm>>();
private static readonly Lazy<MyDataProvider> lazy =
new Lazy<MyDataProvider>(() => new MyDataProvider());
public static MyDataProvider Instance => lazy.Value;
private MyDataProvider()
{
}
public ObservableCollection<LookUpVm> GreenLookupDataSource => GetLookupDataSource(Types.Green);
public ObservableCollection<LookUpVm> GetLookupDataSource(Types lookupEnum)
{
ObservableCollection<LookUpVm> lookupDataSource;
if (_lookupData.TryGetValue(lookupEnum, out lookupDataSource))
return lookupDataSource;
lookupDataSource = new ObservableCollection<LookUpVm>();
var returnedlookupDataSource =
SomeMasterSource.GetlookupDataSourceBylookupEnum(lookupEnum).OrderBy(ia => ia.Name);
foreach (var returnedLookupData in returnedlookupDataSource)
{
lookupDataSource.Add(returnedLookupData);
}
_lookupData.TryAdd(lookupEnum, lookupDataSource);
return lookupDataSource;
}
}
This works great for the 0th iteration, where I create a GreenLookupComboBox.
<ComboBox ItemsSource="{Binding Source={x:Static objectDataProviders:MyDataProvider.Instance},
Path=GreenLookupDataSource}" />
However, what I really need to be able to do is to set up a combobox which can have its Types enum value set on the parent View, which would then call directly to the GetLookupDataSource and pass the enum. We have several dozen lookup types, and defining a new property for each feels less than ideal. Something like the below for the control view...
<ComboBox ItemsSource="{Binding Source={x:Static objectDataProviders:MyDataProvider.Instance},
Path=GetLookupDataSource}" />
And something like the below for where I use the lookup control.
<local:MyLookupControl Type=Types.Green />
Is this even possible?
EDIT:
Here's an example of what I'm trying to accomplish.
I have two key-value pairs of lists.
ListOne
1 - A
2 - B
3 - C
and
ListTwo
1 - X
2 - Y
3 - Z
They are accessible by calling the method GetList(Enum.LookupType). They share the same ViewModel and View. However, I need to place both of them on a form for my users to select from.
I'm looking for some way to use XAML like the following on the View they appear on.
<local:MyLookupControl Method=GetList Parameter=Enum.ListOne/>
<local:MyLookupControl Method=GetList Parameter=Enum.ListTwo />
This should display a pair of comboboxes, one bound to ListOne and one bound to ListTwo.
You're essentially just trying to set up databinding on a couple controls. This is simple then so long as you have the correct datacontext for the view.
Controls can be bound to properties (which is exactly what you are looking for).
Using your edited example here is how you would do that:
private ObservableCollection<string> _listOne;
private ObservableCollection<string> _listTwo;
private string _selectedListOneItem;
private string _selectedListTwoItem;
public ObservableCollection<string> ListOne
{
get { return _listOne; }
set { _listOne = value; }
}
public ObservableCollection<string> ListTwo
{
get { return _listTwo; }
set { _listTwo = value; }
}
public string SelectedListOneItem
{
get { return _selectedListOneItem; }
set { _selectedListOneItem = value; }
}
public string SelectedListTwoItem
{
get { return _selectedListTwoItem; }
set { _selectedListTwoItem = value; }
}
XAML:
<ComboBox ItemsSource="{Binding ListOne}" SelectedItem="{Binding SelectedListOneItem}"/>
<ComboBox ItemsSource="{Binding ListTwo}" SelectedItem="{Binding SelectedListTwoItem}"/>
You have several options in how you want to load or get your lists. You can either load them in the constructor or for something a bit heavier is load them every time you "get" in the property. I would recommend loading those in the constructor.
What I provided is basically autoprops and can even be further simplified but I wanted to show you that you can also write code in the getter and setter of those properties to further expand on the items. For instance, you may want something on the background to fire off when SelectedListOneItem changes. In this case on the SET of SelectedListOneItem you can set the value but then also run a method/function which may update other properties.
WPF is very dependent on properties to bind between ViewModels and Views. In your response before the edit you are using fields which can't be bound to controls in a view.
EDIT:
If you do plan on updating properties in the ViewModel that would change things on the view you are also going to want to look into INotifyPropertyChanged. By implementing INotifyPropertyChanged the view will be updated/notified when properties are changing. INotifyPropertyChanged comes with it's own event that you must invoke in the setting of a property. Here is also a very helpful method you can call that will fire this event for you much easier.
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
You call this like so:
public string SelectedListOneItem
{
get { return _selectedListOneItem; }
set
{
_selectedListOneItem = value;
OnPropertyChanged();
}
}
That way if anything else in the ViewModel updates SelectedListOneItem that your view will make the appropriate change. In this case it would make the combobox select the new value you set in SelectedListOneItem.
I have a form with two ComboBoxes. One of them is being filled with objects coming from a collection in the ViewModel. When I select a value in this ComboBox, it then should fill the second ComboBox.
What I want to know is what the best way is to go about filling the second ComboBox. I think having yet another collection with the details of the selected value of the first ComboBox in the ViewModel might be a bit wasteful. I think the best way might be to hit the database with the selected value, collecting the corresponding details, and then send them back. How I think this would work is to have the details ComboBox have a binding with the 'master' ComboBox so it can get the selected value. Then ideally, the details ComboBox would then somehow get the values from the database.
Problem is that I just don't know how to implement this with MVVM, and any help would be appreciated!
Just call OnPropertyChanged of the details collection once the selected item changes.
You can pre-populate a background dictionary whose key is the possible master items and whose values are a list of detail list.
Note for the below to work you ViewModel must implement INotifyPropertyChanged
e.g.
public class MyViewModel : INotifyPropertyChanged
{
public IEnumerable<MasterOption> MasterList {get;set;}
public IEnumerable<DetailOption> DetailList {get;set;}
Dictionary<MasterOption,List<DetailOption>> DetailLookup;
MasterOption _SelectedMasterOption;
public MasterOption SelectedMasterOption
{
get { return _SelectedMasterOption;}
set
{
_SelectedMasterOption = value;
LoadDetailsList();
OnPropertyChanged("SelectedMasterOption");
}
void LoadDetailsList()
{
InitDictionary();
if (DetailLookup.ContainsKey(SelectedMasterOption))
DetailList = DetailLookup[SelectedMasterOption];
else
DetailList = null;
OnPropertyChanged("DetailList");
}
void InitDictionary()
{
if (DetailLookup == null)
{
//Grab fill the lookup dictionary with information
}
}
}
Create a method in your ViewModel that gets the data for the second combobox and update with BindingExpression in your codebehind.
private void FirstComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewModel.SelectionChange();
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(SecondComboBox, ComboBox.ItemsSourceProperty);
bindingExpression.UpdateTarget();
}
MVVM pattern is implemented in my Silverlight4 application.
Originally, I worked with ObservableCollection of objects in my ViewModel:
public class SquadViewModel : ViewModelBase<ISquadModel>
{
public SquadViewModel(...) : base(...)
{
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
...
_model.DataReceivedEvent += _model_DataReceivedEvent;
_model.RequestData(...);
}
private void _model_DataReceivedEvent(ObservableCollection<TeamPlayerData> allReadyPlayers, ...)
{
foreach (TeamPlayerData tpd in allReadyPlayers)
{
SquadPlayerViewModel sp = new SquadPlayerViewModel(...);
SquadPlayers.Add(sp);
}
}
...
}
Here is a peacie of XAML code for grid displaying:
xmlns:DataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
...
<DataControls:DataGrid ItemsSource="{Binding SquadPlayers}">
...</DataControls:DataGrid>
and my ViewModel is bound to DataContext property of the view.
This collection (SquadPlayers) is not changed after its creation so I would like to change its type to
List<SquadPlayerViewModel>
. When I did that, I also added
RaisePropertyChanged("SquadPlayers")
in the end of '_model_DataReceivedEvent' method (to notify the grid that list data are changed.
The problem is that on initial displaying grid doesn't show any record... Only when I click on any column header it will do 'sorting' and display all items from the list...
Question1: Why datagrid doesn't contain items initially?
Q2: How to make them displayed automatically?
Thanks.
P.S. Here is a declaration of the new List object in my view-model:
public List<SquadPlayerViewModel> SquadPlayers { get; set; }
You can't use List as a binding source, because List not implement INotifyCollectionChanged it is require for WPF/Silverlight to have knowledge for whether the content of collection is change or not. WPF/Sivlerlight than can take further action.
I don't know why you need List<> on your view model, but If for abstraction reason you can use IList<> instead. but make sure you put instance of ObservableCollection<> on it, not the List<>. No matter what Type you used in your ViewModel Binding Only care about runtime type.
so your code should like this:
//Your declaration
public IList<SquadPlayerViewModel> SquadPlayers { get; set; }
//in your implementation for WPF/Silverlight you should do
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
//but for other reason (for non WPF binding) you can do
SquadPlayers = new List<SquadPlayerViewModel>();
I usually used this approach to abstract my "Proxied" Domain Model that returned by NHibernate.
You'll need to have your SquadPlayers List defined something like this:
private ObservableCollection<SquadPlayerViewModel> _SquadPlayers;
public ObservableCollection<SquadPlayerViewModel> SquadPlayers
{
get
{
return _SquadPlayers;
}
set
{
if (_SquadPlayers== value)
{
return;
}
_SquadPlayers= value;
// Update bindings, no broadcast
RaisePropertyChanged("SquadPlayers");
}
}
The problem is that whilst the PropertyChanged event informs the binding of a "change" the value hasn't actually changed, the collection object is still the same object. Some controls save themselves some percieved unnecessary work if they believe the value hasn't really changed.
Try creating a new instance of the ObservableCollection and assigning to the property. In that case the currently assigned object will differ from the new one you create when data is available.
I have a custom Order class, groups of which are stored in List<Order> and a DataGridView. I think the problem is in my implementation so here's how I'm using it:
In the form enclosing DataGridView (as OrdersDataGrid):
public partial class MainForm : Form
{
public static List<Order> Orders;
public MainForm()
{
// code to populate Orders with values, otherwise sets Orders to new List<Order>();
OrdersDataGrid.DataSource = Orders;
}
Then in another form that adds an Order:
// Save event
public void Save(object sender, EventArgs e) {
Order order = BuildOrder(); // method that constructs an order object from form data
MainForm.Orders.Add(order);
}
From what I can tell from the console this is added successfully. I thought the DataGrid would be updated automatically after this since Orders has changed - is there something I'm missing?
The DataGrid accepts the class since it generates columns from the members.
Since you can't use DataBind on a DataGridView that's uses an object list as it's DataSource here's the solution I found to this:
First replace your List<T> with BindingList<T> - essentially the same thing, except the BindingList acts like DataBind().
Change your T to implement System.ComponentModel.INotifyPropertyChanged:
This involves adding a property:
public event PropertyChangedEventHandler PropertyChanged;
Adding to each variable's set block:
public string Name
{
get { return this.CustomerName; }
set {
this.CustomerName = value;
this.NotifyPropertyChanged("Name");
}
}
And adding another method:
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Sources: Binding a DataGridView to a Collection, Info on converting List to BindingList
You need to rebind the data on your DataGrid.
Shouldn't you rebind the DataGrid after the underlying data source is updated?
Use the code:
OrdersDataGrid.DataSource = Orders;
OrdersDataGrid.DataBind();
You have to Bind new data to your datagrid using
Gridview1.DataBind();
note that whenever you update some list, which is binded to a gridview or any other presenter list control, it just update the list not gridview.
if you really do not like to rebind your Item use IronPython which provided in .net 3.5
Easy one for you all...
I'm new to Silverlight and really missing stuff like DataTables and things. What I'm also currently struggling with is how to get the text of my combobox's currently selected item.
In winforms I would have done:
ComboBox myCombo = new ComboBox.......
string selected = myCombo.Text;
I'm struggling how to get this info out.
The selected item of your combo box is whatever type of item is currently holding. So if you set the binding to a collection of strings, then the selected item will be a string:
string mySelectedValue = ((string)MyComboBox.SelectedItem);
If it is a more complex object you will need to cast and use the expected object. If you have XAML using the list box item class, like:
<ComboBox x:Name="MyComboBox">
<ComboBox.Items>
<ComboBoxItem>
<TextBlock Text="Hello World"/>
</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
Then you would access the selected item like this:
string mySelectedValue =
((TextBlock)((ComboBoxItem)MyComboBox.SelectedItem).Content).Text;
Right, the answer is to use myCombo.SelectionBoxItem.ToString()
For a complex object, use reflection with the DisplayMemberPath property:
var itemType = cbx.SelectedItem.GetType();
var pi = itemType.GetProperty(cbx.DisplayMemberPath);
var stringValue = pi.GetValue(cbx.SelectedItem, null).ToString();
((ComboBoxItem)comboBox1.SelectedItem).Content.ToString()
I got it worked by this statement.
string txt=(comboboxID.SelectedItem as BindingClass).Text.ToString();
string value=(comboboxID.SelectedItem as BindingClass).Value.ToString();
public class BindingClass
{
public string Text
{
set;
get;
}
public string Value
{
set;
get;
}
}
If you have a simple combobox for an array of strings, you can get the selected string using
(string)e.AddedItems[0];
Suppose I have a product list combo and I want to know the selected product name. So in the SelectionChanged Event I write the following code:
private void productCombo_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
string product_type=(string)e.AddedItems[0];
}
myCombo.SelectedItem.Content
will return the content of the ComboBoxItem. This could be a TextBlock, etc. depending on what you have in there, and what you are using for an item template.