Im busy with my app and i walked in some problems when i click on a photo in my listbox PhotoFeed.
I got 1 List<> with in it the strings UrlTumb and UrlFull.
I got 1 ListBox with in it a WrapPanel filled with images wich i set the Image.Source from my UrlTumb.
What my problem is when i click on a photo in my listbox i want to navigate to a new page and display there the original image (UrlFull) now i can only get my UrlTumb from my Image.Source but i want my UrlFull which is stored in the List. Now is my question how do i obtain the UrlFull. So how can i back trace which item i clicked and get the UrlFull from that item so i can send it with my NavigationService.Navigate
I can do it on an dirty way and create an invisible textblock besides the image in my ListBox and put the UrlFull in there but i would like to do it in a proper way
So what do i place in the ????? spot in this line
NavigationService.Navigate(new Uri("/PhotoInfo.xaml?urlfull={0}", ????? , UriKind.Relative));
Greetings Cn
There are multiple options:
Use selected item's index listBox.SelectedIndex to get the index
of the selected property which will correspond to the index in your
source (it might not if you filter the collection using collection
source, but I think that is not the case)
Use selected item listBox.SelectedItem this will return the
SelectedItem which will contain your object. (Note, that if your
selection mode set to multiple, this will return only the firstly
selected item)
Use SelectemItems. It will allow you to get an array of selected
items (Note: this should be normally used only when your list's
selection mode is set to multiple)
Use SelectedValue, which will contain the value of the SelectedItem
(this will save you and extra step.
Use arguments of the Selection changed event AddedItems.
Bellow is the code snippet of 3 options above. x, y, z will all be your selected names (e.g. "Mike")
XAML:
<ListBox x:Name="lb"
ItemsSource="{Binding Names}"
SelectionChanged="NameChanged" />
Code behind:
public class Person
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
private List<Person> people = new List<Person>
{
new Person{Name = "Lewis"},
new Person{Name = "Peter"},
new Person{Name = "Brian"}
};
public List<Person> People
{
get
{
return this.people;
}
set
{
this.people = value;
}
}
private void NameChanged(object sender, SelectionChangedEventArgs e)
{
var x = this.people[lb.SelectedIndex];
var y = lb.SelectedItem;
var z = lb.SelectedItems[0];
var h = lb.SelectedValue;
var u = e.AddedItems[0];
var person = e.AddedItems[0] as Person;
if (person != null)
{
var result = person.Name;
}
}
For the differences between SelectedValue and SelectedItem refer here SelectedItem vs SelectedValue
Related
I want to have a drop down list with 12 choices.
I found that ComboBox is what I need (if there is a better control kindly tell me).
I dragged and drop a combo box into a panel using VS2012 and then clicked on the left arrow that appears on the combo box. The following wizard shows:
As you can see, I am just able to type the name of the choice but not the value of it.
My question is how to get the value of these choices?
What I have tried
I built an array with the same length as the choices, so when the user selects any choice, I get the position of that choice and get the value from that array.
Is there a better way?
You need to use a datatable and then select the value from that.
eg)
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Columns.Add("Description", typeof(string));
dt.Load(reader);
//Setting Values
combobox.ValueMember = "ID";
combobox.DisplayMember = "Description";
combobox.SelectedValue = "ID";
combobox.DataSource = dt;
You can then populate your datatable using:
dt.Rows.Add("1","ComboxDisplay");
Here, the DisplayMember(The dropdown list items) are the Descriptions and the Value is the ID.
You need to include a 'SelectedIndexChanged' Event on your combobox (If using VS then double click the control in Design Mode) to get the new values. Something like:
private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
int ID = Combobox.ValueMember;
string Description = ComboBox.DisplayMember.ToString();
}
You can then use the variables in the rest of your code.
You cannot use the wizard to store values and text. To store DisplayText/Value pair the combobox needs to be connected to some data.
public class ComboboxItem
{
public string DisplayText { get; set; }
public int Value { get; set; }
}
There are two properties on the combobox - DisplayMember and ValueMember. We use these to tell the combobox that - show whats in DisplayMember and the actual value is in ValueMember.
private void DataBind()
{
comboBox1.DisplayMember = "DisplayText";
comboBox1.ValueMember = "Value";
ComboboxItem item = new ComboboxItem();
item.DisplayText = "Item1";
item.Value = 1;
comboBox1.Items.Add(item);
}
To get the value -
int selectedValue = (int)comboBox1.SelectedValue;
I have a DataGrid showing some databases having quite some columns.
I would like that, when the user edit a new row, some values are set automatically.
With the windows form DataGrid that would be easy, since there's RowsAdded event handler.
But how could i handle this with the wpf DataGrid ??
Edit : my DataGrid is bound in Xaml to a public property which is an ITable. When user select a table in a ComboBox, the property is updated with corresponding table.
Yes there's autogenerating column, and the way the user can enter a new row is to edit the last blank row (default behaviour).
You can do this in the LoadingRow event. Try something like this:
private void myDataGrid_LoadingRow(object sender, System.Windows.Controls.DataGridRowEventArgs e)
{
MyObject myObject = e.Row.Item as MyObject;
if (myObject != null)
{
myObject.PropertyOne = "test";
myObject.PropertyTwo = 2;
}
}
Ok i think i got it.
When a DataTable is bound to a DataGrid, a CollectionView is created in order to see it. You can get it by using the (static/shared) CollectionView.GetDefaultView(ThePropertyThatIsBound) method.
Since it implements ICollectionChanged, you can add an event handler to the CollectionChangedEvent.
In the CollectionChanged event handler, if you have a new item (e.NewItems.Count>0) you must check it against System.Windows.Data.CollectionView.NewItemPlaceholder and if it is not a place holder, then it is a brand new item, for wich i can set all default values.
Assign a CollectionViewSource to your DataGrid then listen to the CollectionChanged event as following :
..
public CollectionViewSource ViewSource { get; set; }
..
this.ViewSource = new CollectionViewSource();
this.ViewSource.Source = new List<YourObjectType>();
this.ViewSource.View.CollectionChanged += View_CollectionChanged;
..
private void View_CollectionChanged(object sender,System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems.Count > 0)
{
YourObjectType myObject = e.NewItems[e.NewItems.Count-1] as YourObjectType;
if (myObject != null)
{
myObject.Property = TheValueYouWant;
..
}
}
}
..
<DataGrid ItemsSource="{Binding ViewSource.View}" ../>
I have the following XAML markup:
<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
SelectedValuePath="ProductNumber" />
My View's DataContext is bound to a viewmodel containing a public property called SelectedCustomer. Customer objects contain a FavouriteProduct property of type Product and Product objects contain public properties ProductNumber and ProductName.
The behaviour I'm looking for is to have the SelectedItem of the ComboBox update the Text in the TextBox and vice versa. ComboBox to TextBox works just fine. Selecting any product in the ComboBox updates the TextBox with the product number of that product. However when I try to go the other way I get som strange behaviour. It only works for the items that come before the selected item. I will try to explain:
Consider the following list of products ([Product Number], [Product Name]):
Fanta
Pepsi
Coca Cola
Sprite
Water
Now lets say that the SelectedCustomer's favourite product is Coca Cola (must be a developer). So when the window opens the TextBox reads 3 and the ComboBox reads Coca Cola. Lovely. Now lets change the product number in the TextBox to 2. The ComboBox updates it's value to Pepsi. Now try to change the product number in the TextBox to anything higher then the number for Coca Cola (3). Not so lovely. Selecting either 4 (Sprite) or 5 (Water) makes the ComboBox revert back to Coca Cola. So the behaviour seems to be that anything below the item that you open the window width from the list in the ItemSource does not work. Set it to 1 (Fanta) and none of the others work. Set it to 5 (Water) and they all work. Could this have to do with some initialisation for the ComboBox? Potential bug? Curious if anyone else have seen this behaviour.
UPDATE:
After reading Mike Brown's response I have created properties for SelectedProduct and SelectedProductNumber. The problem I am having with this is that as soon as you select something from the ComboBox you end up in an endless loop where the properties keep updatign each other. Have I implemented the OnPropertyChanged handler incorrectly or is there something I am missing? Here is a snippet of code from my ViewModel:
private int _SelectedProductNumber = -1;
public int SelectedProductNumber
{
get
{
if (_SelectedProductNumber == -1 && SelectedCustomer.Product != null)
_SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
return _SelectedProductNumber;
}
set
{
_SelectedProductNumber = value;
OnPropertyChanged("SelectedProductNumber");
_SelectedProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get
{
if (_SelectedProduct == null)
_SelectedProduct = SelectedCustomer.Product;
return _SelectedProduct;
}
set
{
_SelectedProduct = value;
OnPropertyChanged("SelectedProduct");
_SelectedProductNumber = value.ProductNumber;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
UPDATE 2
I have changed the implementation slightly now by updating the SelectedCustomer.FavouriteProduct from both properties and then using that when reading their values. This now works but I'm not sure it's the 'correct way'.
private int _SelectedProductNumber = 0;
public int SelectedProductNumber
{
get
{
if (SelectedCustomer.Product != null)
_SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
return _SelectedProductNumber;
}
set
{
_SelectedProductNumber = value;
SelectedCustomer.FavouriteProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
OnPropertyChanged("SelectedProductNumber");
OnPropertyChanged("SelectedProduct");
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get
{
if (SelectedCustomer.Product != null)
_SelectedProduct = SelectedCustomer.Product;
return _SelectedProduct;
}
set
{
_SelectedProduct = value;
SelectedCustomer.FavouriteProduct = value;
OnPropertyChanged("SelectedProduct");
OnPropertyChanged("SelectedProductNumber");
}
}
Your aim is not too clear so I have written the folloiwng so support either options I can see.
To keep two elements bound to one item in sync you can set the IsSynchronizedWithCurrentItem="True" on your combobox as shown below:
<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
IsSynchronizedWithCurrentItem="True"
SelectedValuePath="ProductNumber" />
This will mean everything in the current window bound to the same background object will keep in sync and not give the odd behaviours you are seeing.
This quote form this longer MSDN article describes the effect:
The IsSynchronizedWithCurrentItem
attribute is important in that, when
the selection changes, that is what
changes the "current item" as far as
the window is concerned. This tells
the WPF engine that this object is
going to be used to change the current
item. Without this attribute, the
current item in the DataContext won't
change, and therefore your text boxes
will assume that it is still on the
first item in the list.
Then setting the Mode=TwoWay as suggested by the other answer will only ensure that both when you update the textbox the underlying object will be updated and when you update the object the textbox is updated.
This makes the textbox edit the selected items text and not select the item in the combolist with the matching text (which is the alternative think you are may be trying to achieve?)
To achieve the synchronised selection effect it may be worth setting IsEditable="True" on the combobox to allow users to type items in and dropping the text box. Alternatively if you need two boxes replace the textbox with a second combobox with IsSynchronizedWithCurrentItem="True" and IsEditable="True" then a styled to make it like a text box.
What you want to do is expose separate properties on your ViewModel for the currently selected product and currently selected product number. When the selected product is changed, update the product number and vice versa. So your viewmodel should look something like this
public class MyViewModel:INotifyPropertyChanged
{
private Product _SelectedProduct;
public Product SelectedProduct
{
get { return _SelectedProduct; }
set
{
_SelectedProduct = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
_SelectedProductID = _SelectedProduct.ID;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID"));
}
}
private int _SelectedProductID;
public int SelectedProductID
{
get { return _SelectedProductID; }
set
{
_SelectedProductID = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID"));
_SelectedProduct = _AvailableProducts.FirstOrDefault(p => p.ID == value);
PropertyChanged(this,new PropertyChangedEventArgs("SelectedProduct"));
}
}
private IEnumerable<Product> _AvailableProducts = GetAvailableProducts();
private static IEnumerable<Product> GetAvailableProducts()
{
return new List<Product>
{
new Product{ID=1, ProductName = "Coke"},
new Product{ID = 2, ProductName="Sprite"},
new Product{ID = 3, ProductName = "Vault"},
new Product{ID=4, ProductName = "Barq's"}
};
}
public IEnumerable<Product> AvailableProducts
{
get { return _AvailableProducts; }
}
private Customer _SelectedCustomer;
public Customer SelectedCustomer
{
get { return _SelectedCustomer; }
set
{
_SelectedCustomer = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));
SelectedProduct = value.FavoriteProduct;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So now your XAML binds to the appropriate properties and the viewModel is responsible for syncrhronization
<TextBox
x:Name="MyTextBox"
Text="{Binding Path=SelectedProductID, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox
x:Name="MyComboBox"
ItemsSource="{Binding AvailableProducts}"
DisplayMemberPath="ProductName"
SelectedItem="{Binding SelectedProduct}" />
Don't forget to implement the rest of INotifyPropertyChanged and the GetAvailableProducts function. Also there may be some errors. I hand typed this here instead of using VS but you should get the general idea.
Try:
SelectedItem="{Binding Path=YourPath, Mode=TwoWay"}
instead of setting SelectedValue and SelectedValuePath.
Might work with SelectedValue too, don't forget the Mode=TwoWay, since this isn't the default.
A good approuch would to use the master detail pattern - bind the master (the items view, e.g. combobox) to the data source collection and the detail view (e.g. text box) to the selected item in the source collection, using a binding converter to read/write the appropriate property.
Here is an example:
http://blogs.microsoft.co.il/blogs/tomershamam/archive/2008/03/28/63397.aspx
Notice the master binding is of the form {Binding} or {Binding SourceCollection} and the details binding is of the form {Binding } or {Binding SourceCollection}.
To get this working you need to wrap you collection with an object that keeps the selected item. WPF has one of these built-in: ObjectDataProvider.
Example:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/068977c9-95a8-4b4a-9d38-b0cc36d06446
I have been confused while setting SelectedItem programmaticaly in wpf applications with Net Framework 3.5 sp1 installed. I have carefully read about hundred posts \topics but still confused((
My xaml:
<ComboBox name="cbTheme">
<ComboBoxItem>Sunrise theme</ComboBoxItem>
<ComboBoxItem>Sunset theme</ComboBoxItem>
</ComboBox>
If I add IsSelected="True" property in one of the items - it's dosn't sets this item selected. WHY ?
And i was try different in code and still can't set selected item:
cbTheme.SelectedItem=cbTheme.Items.GetItemAt(1); //dosn't work
cbTheme.Text = "Sunrise theme"; //dosn't work
cbTheme.Text = cbTheme.Items.GetItemAt(1).ToString();//dosn't work
cbTheme.SelectedValue = ...//dosn't work
cbTheme.SelectedValuePath = .. //dosn't work
//and even this dosn't work:
ComboBoxItem selcbi = (ComboBoxItem)cbTheme.Items.GetItemAt(1);//or selcbi = new ComboBoxItem
cbTheme.SelectedItem = selcbi;
The SelectedItem is not readonly property, so why it wan't work?
I think thats should be a Microsoft's problems, not my. Or I have missed something??? I have try playing with ListBox, and all work fine with same code, I can set selections, get selections and so on.... So what can I do with ComboBox ? Maybe some tricks ???
To select any item in the ComboBox and to set it as default item selected just use the below line:
combobox.SelectedIndex = 0; //index should be the index of item which you want to be selected
If i add the combobox and items programmatically, this works for me:
ComboBox newCombo = new ComboBox();
ComboBoxItem newitem = new ComboBoxItem();
newitem.Content = "test 1";
newCombo.Items.Add(newitem);
newitem = new ComboBoxItem();
newitem.Content = "test 2";
newCombo.Items.Add(newitem);
newitem = new ComboBoxItem();
newitem.Content = "test 3";
newCombo.Items.Add(newitem);
newCombo.SelectedItem = ((ComboBoxItem)newCombo.Items[1]);
newCombo.Text = ((ComboBoxItem)newCombo.Items[1]).Content.ToString();
newStack.Children.Add(newCombo);
It also works if it set the ItemSource property programmatically, then set the text to the selected value.
Create a public property in your viewmodel for the theme list and one for the selected item:
private IEnumerable<string> _themeList;
public IEnumerable<string> ThemeList
{
get { return _themeList; }
set
{
_themeList = value;
PropertyChangedEvent.Notify(this, "ThemeList");
}
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
PropertyChangedEvent.Notify(this,"SelectedItem");
}
}
bind your combobox in xaml to the properties like this:
<ComboBox
Name="cbTheme"
ItemsSource="{Binding ThemeList}"
SelectedItem="{Binding SelectedItem}">
</ComboBox>
now all you do is add items to the ThemeList to populate the combobox. To select an item in the list, set the selected property to the text of the item you want selected like this:
var tmpList = new List<string>();
tmpList.Add("Sunrise theme");
tmpList.Add("Sunset theme");
_viewModel.ThemeList = tmpList;
_viewModel.SelectedItem = "Sunset theme";
or try setting the selected item to the string value of the item you want selected in your own code if you want to use the code you currently have - not sure if it will work but you can try.
If you know the index of the item you want to set, in this case it looks like you are trying to set index 1, you simply do:
cbTheme.SelectedIndex = 1;
I found that when you don't know the index, that's when you have the real issue. I know this goes beyond the original question, but for Googlers on that count that want to know how to set the item when the index isn't known but the value you want to display IS known, if you are filling your dropdown with an ItemSource from a DataTable, for example, you can get that index by doing this:
int matchedIndex = 0;
if (dt != null & dt.Rows != null)
{
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
string myRowValue = dr["SOME_COLUMN"] != null ? dr["SOME_COLUMN"].ToString() : String.Empty;
if (myRowValue == "Value I Want To Set")
break;
else
matchedIndex++;
}
}
}
And then you do simply do cbTheme.SelectedIndex = matchedIndex;.
A similar iteration of ComboBoxItem items instead of DataRow rows could yield a similar result, if the ComboBox was filled how the OP shows, instead.
Is the ComboBox data bound?
If so you are probably better to do it through Binding rather than code ....
See this question ... WPF ListView Programmatically Select Item
Maybe create a new SelectableObject {Text = "Abc Theme", IsCurrentlySelected = True}
Bind a collection of SelectableObjects to the ComboBox.
Essentially setting the IsCurrentlySelected property in the model and having UI update from the Model.
Acording Answer 4
If you already add the Items in the Item source.
Fire the PropertyChangedEvent of the selectet Value.
tmpList.Add("Sunrise theme");
tmpList.Add("Sunset theme");
PropertyChangedEvent.Notify(this,"SelectedItem");
I've been using WinForms databinding to display data from a database mapped with Fluent NHibernate, and that's been working great.
For example, I can just set a DataGridView's DataSource property from an entity's IList property, and voila - there's all the data!
But now I need to start adding and saving new data rows, and that's not going so well. I thought I'd be able to just enable the grid's AllowUserToAddRows property, and new rows would get added to the underlying IList in the entity, but that didn't work.
Then, after a little searching, I tried setting the DataSource property to a BindingList that was populated from the IList, but that's not being updated with new rows either.
During the course of my searches, I also came upon a few people reporting difficulty with WinForms and DataBinding in general, which makes me wonder if I should pursue that approach any further.
Is the DataBinding approach worth continuing? If so, can anyone suggest where I'm going wrong?
Or is it better to just handle all the DataGridView events associated with adding a new row, and writing my own code to add new objects to the IList property in my entity?
Other suggestions? (though I don't think switching to WPF is going to be an option, no matter how much better the databinding may be)
Can you load (or copy) your nHibernate entities into a generic List? If so, I have had good success in with two-way binding using a DataGridView bound to a generic List.
The key points are:
The generic list contains list objects where each is an instance of your custom class.
Your custom class must implement public properties for each of the fields to bind. Public fields didn't work for me.
Use a BindingSource to wrap the actual generic list.
The BindingSOurce allows you to set the AllowNew property to true. Binding directly to the List almost works, but the DataGridVieww does not display the "New row" line, even if AllowUsersToAddRows = true.
For example, add this code to a Form with a dataGridView1:
private List<MyObject> m_data = new List<MyObject>();
private BindingSource m_bs =new BindingSource();
private void Form1_Load(object sender, EventArgs e)
{
m_data.Add(new MyObject(0,"One",DateTime.Now));
m_data.Add(new MyObject(1, "Two", DateTime.Now));
m_data.Add(new MyObject(2, "Three", DateTime.Now));
m_bs.DataSource = m_data;
m_bs.AllowNew = true;
dataGridView1.DataSource = m_bs;
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AllowUserToAddRows = true;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for (int i = 0; i < m_data.Count ; i++)
{
Console.WriteLine(string.Format("{0} {1} {2}", m_data[i].ID, m_data[i].Name, m_data[i].DOB));
}
}
}
public class MyObject
{
// Default ctor, required for adding new rows in DataGridView
public MyObject()
{
}
public MyObject(int id, string name, DateTime dob)
{
ID = id;
Name = name;
DOB = dob;
}
private int m_id;
public int ID
{
get
{
return m_id;
}
set
{
m_id = value;
}
}
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
private DateTime m_dob;
public DateTime DOB
{
get
{
return m_dob;
}
set
{
m_dob = value;
}
}
}
When the form closes, the contents of the bound List are printed to the Output window.