I have a listbox that the text is not updating when I change the underlying objects properties:
ClassA testObj = new ClassA(){name="myname"};
ListBox1.items.Add(testObj);
Class ClassA
{
public string name {get;set;}
public override string ToString()
{
return name;
}
}
so if I update the ClassA.name property the Listbox does not update. I have tried to call the listboxes refresh() method.
Try replacing the item in the list with a new reference:
ClassA item = (ClassA)listBox1.Items[0];
item.name = "New Item";
listBox1.Items[0] = item;
Unfortunately, a bad side effect of this is it fires the SelectedIndexChanged event multiple times, so you would need to remove the handler and add it back in or set a flag to work around that behavior.
Or just have your class implement INotifyPropertyChanged and have your list use a BindingList<ClassA> as it's DataSource, then any changes to an item's property would automatically update the ListBox.
Your class "ClassA" should inherit an interface called "INumerable"
Related
I have a ListView and a GridView that lists users in an application by names. Whenever the user selects an user to edit, I add a new tab to a TabControl, and bind all editable properties to the WPF controls.
However, when the user is editing in the Edit Tab, the information in the List (specifically, the name field) is also being updated.
Currently I'm making a copy of the object to be edited and leaving the original so it doesn't update the ListView, but isn't there a better/easier way to do this?
I've tried setting the Binding Mode=OneWay, didn't work, and also UpdateSourceTrigger=Explicit in the GridView but also didn't work.
Is there any easier way to do this?
Edit: The way I implemented my INotifyPropertyChanged class is part of the issue, since I have this:
public partial class MyTabControl : UserControl
{
public MyTabControl()
{
InitializeComponent();
//Here, DataContext is a List<Users>
//Users being my Model from the Database
//Some of it's properties are bound to a GridView
//User doesn't implement INPC
}
public void OpenTab(object sender, RoutedEventArgs e)
{
User original = (sender as Button).DataContext as User;
// - This will create a new ViewModel below with the User I'm sending
MyTabControl.AddTab(original);
}
}
And my ViewModel of Users is:
public class UserViewModel : INotifyPropertyChanged
{
public User Original { get; private set; }
public string Name { get { return Original.Name; } set { Original.Name = value; OnPropertyChanged("Name"); } }
public UserViewModel(User original)
{
Original = original ?? new User();
}
// - INPC implementation
}
Since my ViewModel is the one reporting the property changes, I didn't expect my original User to report it as well to the GridView.
The Mode=OneWay causes the information flow to go from the bound data entity to the target UI property only, any change to the UI property will not be bound back.
The reason why the UI content is changing is because the underlying property is read/write (i.e. has a getter and a setter) and is notifying any value change (due to the implementation of the INPC interface).
Presuming that it is a list of User objects you've bound to the GridView, you have two simple options to fix this. Which one is best depends on how much scope for change you have:
change the current Name property on the User object, remove the setter for it. Replace the setter with a method to set the property (i.e. SetUserName(string name)) which then sets the private member variable. Or pass the name as an argument to the constructor of the User entity.
create a new property with only a getter which returns the name and set your binding to that; i.e. public string UserName { get { return Name; }}. As there is only a getter there will be no notification of this property, so if the name does change it won't be propagated via this new property.
I currently have a ComboBox in my Windows Forms Application. In order to specify which values the ComboBox will contain, I set DataSource property of the ComboBox to some array so that ComboBox contains values from that array. I could also use Items.Add() to add new values to ComboBox. However, I want to make sure that ComboBox can be populated with objects of some specific type. So, if I have a class called X, then I want to make it so that only an array of type X can be used as a data source for the ComboBox. Right now, ComboBox accepts objects of type System.Object. How can I achieve it? Is there a property of ComboBox that I need to set to be equal to my data type's name? Or is there an event that will check whether an object added to my ComboBox is of the needed type and will throw an exception if not?
I was thinking of creating a new class as a subtype of ComboBox, and overriding the Add method of Items property so that Add checks whether its argument is of the needed type (not sure if and how I can do it). Even if I do that, there are still other ways to add new values into ComboBox (AddRange, CopyTo, etc.), so I think there should be a more elegant solution to this problem.
If you want to control the type of item that the ComboBox can contain, you could try creating a new class derived form ComboBox, but you'd run into the problem that it still has the ComboBox.ObjectCollection Items property which would still accept any type! And (unfortunately for your idea of overriding) the Add method isn't virtual.
The only practical solution that I could think of would be to abstract the ComboBox somehow. If this isn't shared code, I would recommend just creating a method that you would use to add items to the ComboBox. Something like:
// NOTE: All items that are added to comboBox1 need to be of type `SomeType`.
private void AddItemToComboBox(SomeType item)
{
comboBox1.Items.Add(item);
}
Any attempt to add a non-SomeType object to the ComboBox would be met with a compiler error. Unfortunately, there's no easy way to prevent someone from still adding a non-SomeType item to ComboBox.Items directly.
Again, if this isn't shared code, it shouldn't really be an issue.
You can hide Items property by your
own Items property of custom type which taking as parameter original ItemsCollection
Example class for testing
public class Order
{
public Int32 ID { get; set; }
public string Reference { get; set; }
public Order() { }
public Order(Int32 inID, string inReference)
{
this.ID = inID;
this.Reference = (inReference == null) ? string.Empty : inReference;
}
//Very important
//Because ComboBox using .ToString method for showing Items in the list
public override string ToString()
{
return this.Reference;
}
}
With next class I tried wrap ComboBox's items collection in own type.
Where adding items must be concrete type
Here you can add other methods/properties you need (Remove)
public class ComboBoxList<TCustomType>
{
private System.Windows.Forms.ComboBox.ObjectCollection _baseList;
public ComboBoxList(System.Windows.Forms.ComboBox.ObjectCollection baseItems)
{
_baseList = baseItems;
}
public TCustomType this[Int32 index]
{
get { return (TCustomType)_baseList[index]; }
set { _baseList[index] = value; }
}
public void Add(TCustomType item)
{
_baseList.Add(item);
}
public Int32 Count { get { return _baseList.Count; } }
}
Here custom combobox class derived from ComboBox
Added: generic type
public class ComboBoxCustomType<TCustomType> : System.Windows.Forms.ComboBox
{
//Hide base.Items property by our wrapping class
public new ComboBoxList<TCustomType> Items;
public ComboBoxCustomType() : base()
{
this.Items = new ComboBoxList<TCustomType>(base.Items);
}
public new TCustomType SelectedItem
{
get { return (TCustomType)base.SelectedItem; }
}
}
Next code used in the Form
private ComboBoxCustomType<Order> _cmbCustom;
//this method used in constructor of the Form
private void ComboBoxCustomType_Initialize()
{
_cmbCustom = new ComboBoxCustomType<Order>();
_cmbCustom.Location = new Point(100, 20);
_cmbCustom.Visible = true;
_cmbCustom.DropDownStyle = ComboBoxStyle.DropDownList;
_cmbCustom.Items.Add(new Order(0, " - nothing - "));
_cmbCustom.Items.Add(new Order(1, "One"));
_cmbCustom.Items.Add(new Order(2, "Three"));
_cmbCustom.Items.Add(new Order(3, "Four"));
_cmbCustom.SelectedIndex = 0;
this.Controls.Add(_cmbCustom);
}
Instead of overriding ComboBox (which wont work as stated in itsme86's answer) you could override usercontrol, add a combobox to this, and then only expose the elements that you wish to work with. Something similar to
public partial class MyComboBox<T> : UserControl where T: class
{
public MyComboBox()
{
InitializeComponent();
}
public void Add(T item)
{
comboBox1.Items.Add(item);
}
public IEnumerable<T> Items
{
get { return comboBox1.Items.Cast<T>(); }
}
}
Please note however that some pieces of automated software rely on access the the underlying controls however so this may cause some issues.
This approach never changes the Items of the combobox so they will still store as objects but when you access them, you are casting them to the correct type and only allowing them to be added of that type. You can create a new combobox via
var myCB = new MyComboBox<ItemClass>();
I can't understand what's happening here. I have two public properties in my ViewModel:
public ObservableCollection<vw_ClientesFull> MyClients { get; set; }
private IEnumerable<vw_ClientesFull> _clients;
public IEnumerable<vw_ClientesFull> Clients
{
get
{
return _clients;
}
set
{
_clients= value;
OnPropertyChanged("Clients");
}
}
Then I have a method to refresh both of them:
private void RefreshClientes()
{
this.serviceClient.Clientes_ListarCompleted += (s, e) =>
{
Clients = e.Result;
MyClients = new ObservableCollection<vw_ClientesFull>(Clients);
};
this.serviceClient.Clientes_ListarAsync(_sRazonSocial, VendedorSel, TransporteSel, _nID, bInactivos);
}
Them i bind my dataGrid to show the information. If I do:
ItemsSource="{Binding Path=Clients}"
If works perfect, but if i do:
ItemsSource="{Binding Path=MyClients}"
Nothing is show! Why? Doesn't ObservableCollection fire onPropertyChange Automaticaly?
Thanks for the help!!!
UPDATE
So if i need to fire the OnPropertyChange manualy, why this work without it?
public ObservableCollection<Vendedores> Vendedores { get; set; }
private void CargarVendedores()
{
Vendedores = new ObservableCollection<Vendedores>(this.serviceClient.GetVendedores());
this.VendedorSel = this.Vendedores.FirstOrDefault();
}
If i bind a combobox like this:
ItemsSource="{Binding Path=Vendedores}"
Work without the OnPropertyChange! Why!
This problem is due to a misconception. ObservableCollection does not raise PropertyChanged, (which happens when the entire property is reassigned) when you replace it, but rather CollectionChanged (which is raised when items are added or removed). You still need to raise PropertyChanged if you plan to reassign the whole object.
Yes, ObservableCollection implements INotifyPropretyChanged. However, it isn't going to help you here :)
ObservableCollection is special because it implements INotifyCollectionChanged. In other words, it raises an event when items are added to or removed from the underlying collection. It also implements INotifyPropertyChanged, so anything bound to a property of the collection will get updated.
You are changing the variable itself though (setting to a new instance no less). This requires that the "instance" of the ObservableCollection property raise the event. In other words, you need:
private ObservableCollection<vw_ClientesFull> myClients;
public ObservableCollection<vw_ClientesFull> MyClients
{
get { return myClients; }
set
{
myClients = value;
OnPropertyChanged("MyClients");
}
In your update, the binding hasn't fired yet (you set in the constructor) so it gets the correct list. Subsequent changes to the list wouldn't work, however.
You must raise the PropertyChanged event when you set the value of MyClients, same as you've already done for Clients.
I have a ComboBox with few static values.
<ComboBox Name="cmbBoxField" Grid.Column="4" Grid.Row="2" Style="{StaticResource comboBoxStyleFixedWidth}" ItemsSource="{Binding}" ></ComboBox>
MVVMModle1.cmbBoxField.Items.Add(new CustomComboBoxItem("Text Box", "0"));
MVVMModle1.cmbBoxFieldType.Items.Add(new CustomComboBoxItem("Pick List", "1"));
MVVMModle1.cmbBoxFieldType.Items.Add(new CustomComboBoxItem("Check Box", "2"));
MVVMModle1.cmbBoxFieldType.Items.Add(new CustomComboBoxItem("Radio Button", "3"));
When I am saving the data in Database table it is getting saved.
((CustomComboBoxItem)(MVVMModle1.cmbBoxField.SelectedValue)).Value.ToString();
Now when I am trying to Edit my form and binding the value again to combobox it is not showing the value.
MVVMModle1.cmbBoxField.SelectedValue = dtDataList.Rows[0]["ControlList"].ToString().Trim();
Someone please help me in this. How to bind selected value to the combobox?
There are quite a few problems with your code here:
You are setting the ItemsControl.ItemsSource property to the default binding (bind to the current data context), which is incorrect unless the DataContext is any type that implements IEnumerable, which it probably isn't.
If this is correct because the DataContext is, for example, an ObservableCollection<T>, then you still have an issue because you are adding items statically to the ComboBox instead of whatever the ItemsSource is.
Also, the type of items you are adding are CustomComboBoxItem, which I'm going to assume inherits from ComboBoxItem. Either way, you can't say the SelectedValue is some string since the values in the ComboBox are not strings.
You should really not have a collection of CustomComboBoxItem's, but instead a custom class that is in itself it's own ViewModel.
Now that that's been said, here is a suggested solution to your problem:
<ComboBox ItemsSource="{Binding Path=MyCollection}"
SelectedValue="{Binding Path=MySelectedString}"
SelectedValuePath="StringProp" />
public class CustomComboBoxItem : ComboBoxItem
{
// Not sure what the property name is...
public string StringProp { get; set; }
...
}
// I'm assuming you don't have a separate ViewModel class and you're using
// the actual window/page as your ViewModel (which you shouldn't do...)
public class MyWPFWindow : Window, INotifyPropertyChanged
{
public MyWPFWindow()
{
MyCollection = new ObservableCollection<CustomComboBoxItem>();
// Add values somewhere in code, doesn't have to be here...
MyCollection.Add(new CustomComboBoxItem("Text Box", "0"));
etc ...
InitializeComponent();
}
public ObservableCollection<CustomComboBoxItem> MyCollection
{
get;
private set;
}
private string _mySelectedString;
public string MySelectedString
{
get { return _mySelectedString; }
set
{
if (String.Equals(value, _mySelectedString)) return;
_mySelectedString = value;
RaisePropertyChanged("MySelectedString");
}
}
public void GetStringFromDb()
{
// ...
MySelectedString = dtDataList.Rows[0]["ControlList"].ToString().Trim();
}
}
You could alternatively not implement INotifyPropertyChanged and use a DependencyProperty for your MySelectedString property, but using INPC is the preferred way. Anyways, that should give you enough information to know which direction to head in...
TL;DR;
Take advantage of binding to an ObservableCollection<T> (create a property for this).
Add your items (CustomComboBoxItems) to the ObservableCollection<T>.
Bind the ItemsSource to the new collection property you created.
Bind the SelectedValue to some string property you create (take advantage of INPC).
Set the SelectedValuePath to the path of the string property name of your CustomComboBoxItem.
Can you use cmbBoxField.DataBoundItem()? If not target the source from the selected value, i.e. Get the ID then query the source again to get the data.
(CustomComboBoxItem)MVVMModle1.cmbBoxField.DataBoundItem();
When you bind a datasource it is simpler to do it like this:
private List GetItems(){
List<CustomComboBoxItem> items = new List<CustomComboBoxItem>();
items.Add(new CustomComboBoxItem() {Prop1 = "Text Box", Prop2 = "0"});
//...and so on
return items;
}
Then in your main code:
List<CustomComboBoxItem> items = this.GetItems();
MVVMModle1.cmbBoxField.DisplayMember = Prop1;
MVVMModle1.cmbBoxField.ValueMember = Prop2;
MVVMModle1.cmbBoxField.DataSource = items;
This will then allow your selected value to work, either select by index, value or text
var selected = dtDataList.Rows[0]["ControlList"].ToString().Trim();
MVVMModle1.cmbBoxField.SelectedValue = selected;
i'm having trouble getting a clear answer for this.
I have a Static class (DataHolder) that holds a static list with a complex type (CustomerName and CustomerID properties).
I want to bind it to a ListBox in WPF but add another item that will have the word "All" for future drag and drop capablilities.
Anyone?
Create a ViewModel Class you can databind to! The ViewModel can reference the static class and copy the items to its own collection and add the all item to it.
Like this
public class YourViewModel
{
public virtual ObservableCollection<YourComplexType> YourCollection
{
get
{
var list = new ObservableCollection<YourComplexType>(YourStaticClass.YourList);
var allEntity = new YourComplexType();
allEntity.Name = "all";
allEntity.Id = 0;
list.Insert(0, allEntity);
return list;
}
}
}
Note, sometimes, you need empty Items. Since WPF can't databind to null values you need to use the same approach to handle it. The empty business entity has been a best practice for it. Just google it.
That "All" item has to be part of the list you bind your ListBox against. Natuarally you can not add that item to the DataHolder list because it holds items of type Customer (or similar). You could of course add a "magic" Customer that always acts as the "All" item but that is for obvious reasons a serious case of design smell (it is a list of Customers after all).
What you could do, is to not bind against the DataHolder list directly but introduce a wrapper. This wrapper would be your ViewModel. You would bind your ListBox agains a list of CustomerListItemViewModel that represents either a Customer or the "All" item.
CustomerViewModel
{
string Id { get; private set; }
string Name { get; set; }
public static readonly CustomerViewModel All { get; private set; }
static CustomerViewModel()
{
// set up the one and only "All" item
All = new CustomerViewModel();
All.Name = ResourceStrings.All;
}
private CustomerViewModel()
{
}
public CustomerViewModel(Customer actualCustomer)
{
this.Name = actualCustomer.Name;
this.Id = actualCustomer.Id;
}
}
someOtherViewModel.Customers = new ObservableCollection<CustomerViewModel>();
// add all the wrapping CustomerViewModel instances to the collection
someOtherViewModel.Customers.Add(CustomerViewModel.All);
And then in your Drag&Drop code somewhere in the ViewModel:
if(tragetCustomerViewModelItem = CustomerViewModel.All)
{
// something was dropped to the "All" item
}
I might have just introduced you to the benefits of MVVM in WPF. It saves you a lot of hassle in the long run.
If you use binding than the data provided as the source has to hold all of the items, ie. you can't databind and then add another item to the list.
You should add the "All" item to the DataHolder collection, and handle the 'All' item separately in your code.