Why do widgets update simultaneously when assigned to the same data source? - winforms

I have a very basic question about Windows Forms datasources.
If I assign the same object data source to a combobox and a listbox on the same form I observe interesting UI behavior: when I change the item in a combobox (or listbox) the other control selects the same item.
I have no extra code for this UI behavior so I wonder how it works.
var persons = new List<Person>
{
new Person {Id = 1, Age = 10, Name = "Alex"},
new Person {Id = 2, Age = 12, Name = "Boris"},
};
// ListBox
lbPersons.DisplayMember = "Name";
lbPersons.DataSource = persons;
// ComboBox
cbPersons.DisplayMember = "Name";
cbPersons.DataSource = persons;
Please, explain how a control's selected item is changed synchronously?

I have found some similar problems on the web. I don't fully understand how this works, but I will give my best shot at it:
When you have a bind multiple controls to the same datasource, they use the same bindingcontext. Therefore, switching the selected item on one control will change the the selected item on the other control.
Instead when you bind the datasources, give each one a new BindingContext:
lbPersons.DisplayMember = "Name";
lbPersons.DataSource = persons;
lbPersons.BindingContext = new BindingContext();
cbPersons.DisplayMember = "Name";
cbPersons.DataSource = persons;
cbPersons.BindingContext = new BindingContext();
I found this info in a forum (link below) where they confirm the issue and have a solution. I need to do more reading on this, but more info can be found in the msdn at:
http://bytes.com/topic/c-sharp/answers/850851-multiple-controls-bound-same-data-source
http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingcontext.aspx
EDIT as per:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.bindingcontext(v=vs.71).aspx
The BindingContext object of a Control is used to return a single
BindingManagerBase object for all data-bound controls contained by the
Control. The BindingManagerBase object keeps all controls that are
bound to the same data source synchronized. For example, setting the
Position property of the BindingManagerBase specifies the item in the
underlying list that all data-bound controls point to.
Also:
(http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingcontext.bindingcontext(v=vs.71).aspx)
For example, if you have two BindingManagerBase objects (from two
different BindingContext objects), you can set the Position properties
of each BindingManagerBase to different values causing each set of
data-bound controls will display different values from the same data
source.

Related

Tricky binding in WPF

I have a list of countries and a list of translations in different languages for each country, this is available in my ViewModel as Countries and Translations.
I want to display each country and its translations in ONE datagrid row.
Each country may have different translations filled.
The datagrid I am using allows to add new columns at runtime.
I managed to show this using a multibinding:
foreach (var language in languages)
{
Binding translationsBinding = new Binding { Path = new PropertyPath("Translations") };
Binding languageBinding = new Binding { Source = language.ID };
MultiBinding multiBinding = new MultiBinding();
multiBinding.Converter = new TranslationsMultiConverter();
multiBinding.Mode = BindingMode.TwoWay;
multiBinding.Bindings.Add(translationsBinding);
multiBinding.Bindings.Add(languageBinding);
}
My question is: how can I set a changed translation back to my Translations list, by using a binding only?
Regards

Simple- How To select an Item from a WPF combo box using coded UI

I have worked with coded UI for web applications but for the first time I am trying to leverage Coded UI for WPF. I want to perform a click on an item from a combobox But, I am unable to achieve the same here in WPF. I tried to loop through the items inside the combo box but It did not work. Tried with Search Property - No result. Also, was trying to figure out the AutomationElement stuff but not able to get a solution. It would be great if I can get an idea of the approach that needs to be followed to achieve the requirement.
I have captured the controls and want to play around with them. No record and playback.
You can use the WpfComboBox's SelectedItem property which takes the name of the item you want to select (as mentioned in my comment in yonder answer)
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
myComboBox.SelectedItem = items[0].Name;
or you can simply set the SelectedIndex if you already know the index of the item you want to set
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
myComboBox.SelectedIndex = 0;
or you can first click the combobox to get it expanded and then get the UITestControl for the item element and perform a click on it (unfortunately you have to manually click the combobox because it seems that the ExpandWhileSearching configuration it doesn't work on it)
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
Mouse.Click(myComboBox);
Mouse.Click(items[0]);
or
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
myComboBox.Expanded = true;
Mouse.Click(items[0]);
You would create the combobox object the same way you would in Html, just using Microsoft.VisualStudio.TestTools.UITesting.WpfControls namespace. For example:
public WpfComboBox tester
{
get
{
WpfComboBox target = new WpfComboBox();
return target;
}
}
Then, you will create a UITestControlCollection object to store the .Items of the combo box.
UITestControlCollection comboBoxItems = tester.Items;
From here, you should be able to edit and set selected items ( tester.SelectedItem = comboBoxItems[0].ToString();) at will.

Datagrid is not binding when using ObservableCollection<object>

Hi i am new silverlight application development. i want to bind a data grid from code behind
for that i have
ObservableCollection<object> GridDataSource
As datasource, and i use it as
dgAvailibilityOption.ItemsSource = GridDataSource;
Now despite this data grid not showing any records. For a check if i bind
List<int> testint = new List<int>();
//Add data to list 1 to 10 e.g
dgAvailibilityOption.ItemsSource = testint
then datagrid shows perfect rsults as expected
Now my qiestion is that, is there any problem in taking ObservableCollection type for binding the datagrid??
Convert to List while assigning to datagrid,
Also check you have items inside the collection,
dgAvailibilityOption.ItemsSource = GridDataSource.ToList();
I assume you have, AutoGenerateColumns to be true in GridDataSource.

Where the combobox bound items are coming from?

May be it's a silly (or more than trivial) kinda question, but it seems i just don't know the answer. Here's the case -
I assigned a UserList as the ItemsSource of a combobox. So what i did essentially is assigning a reference type to another.
I cleared the UserList. So now i get the Count of the ItemsSource 0 as well.
I still get the items present in my combobox. And i also can cast the SelectedItem of the combobox to a User object.
Here's the complete code -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class MainWindow : Window
{
private List<User> _userList;
public MainWindow()
{
InitializeComponent();
_userList = new List<User>()
{
new User() {Id = 1, Name = "X"},
new User() {Id = 2, Name = "Y"},
new User() {Id = 3, Name = "Z"}
};
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBox1.ItemsSource = _userList;
this.comboBox1.DisplayMemberPath = "Name";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_userList.Clear();
/* ItemsSource is cleared as well*/
IEnumerable userList = this.comboBox1.ItemsSource;
/*I can still get my User*/
User user = this.comboBox1.SelectedItem as User;
}
}
So, where the items are coming from? What actually happens under-the-hood when i make such binding? Does the control have some kind of cache? It's a royal pain to realize not having such basic ideas. Can anybody explain the behind-the-scene detail?
EDIT : I wrote the code in WPF, but i have the same question for WinForms Combobox.
EDIT : Doesn't a combobox display its items from it's in-memory Datasource? When that datasource contains 0 items, how does it display the items?
When you set an ItemsSource of any ItemsControl it copies the ref to the list into its Items property. Then it subscribes to the OnCollectionChanged event, and creates a CollectionView object. So, on the screen you can see that collectionView.
as I have found in source code ItemCollection holds two lists:
internal void SetItemsSource(IEnumerable value)
{
//checks are missed
this._itemsSource = value;
this.SetCollectionView(CollectionViewSource.GetDefaultCollectionView((object) this._itemsSource, this.ModelParent));
}
How could you get SelectedItem?
This is my assumption from quick look into the source code:
ItemsControl has a collection of "views" and each View sholud store a ref to the item (User instance), because it has to draw data on the screen. So, when you call SelectedItem it returns a saved ref.
Upd about references
Assume there is an User instance. It has the adress 123 in memory. There is a list. It stores references. One of them is 123.
When you set an ItemsSource ItemsControl saves a reference to the list, and creates a Views collection. Each view stores a references to an item. One view stores an address 123.
Then you cleared a list of users. Now list doesn't contains any references to Users. But in memory there is an adrress 123 and there is an instance of User by this adress. Garbage Collector doesn't destroy it, because View has a reference to it.
When you get SelectedItem it returns User instance from the 123 adress.
var user = new User();
var list = new List<User>();
list.Add(user);
list.Clear();
Console.WriteLine(list.Count()); //prints 0 - list is empty
Console.WriteLine(user == null); //prints false. - user instance is sill exists;
In answer to your comment to #GazTheDestroyer ("... why it doesn't get cleared, and how it holds the items?")
In WPF, when you set the ItemsSource property of an ItemsControl, the control will wrap the list of items in a CollectionView, which is a collection type optimised for use by the UI framework. This CollectionView is assigned to the Items property of the control and is what the display-drawing code actually works from. As you see, this collection is entirely separate of the object you originally assigned to ItemsSource, and so there is no propogation of changes from one to the other. This is why the items are still in the control when you clear the original list: the control is ignoring the original list, and has its own list that contains your objects.
It's for this reason that an ItemsSource value needs to raise events - specifically INotifyCollectionChanged.NotifyCollectionChanged - so that the control knows to refresh the Items list. ObservableCollection implements this interface and raises the correct event, and so the functionality works as expected.
It's hugely important to note that this is nothing like what happens in WinForms, which is why I've been pressing you for the clarification.
EDIT: To clarify, there is no "deep copy." The code that is happening is similar in principle to the following:
private List<object> myCopy;
public void SetItemsSource(List<object> yourCopy)
{
myCopy = new List<object>();
foreach (var o in yourCopy)
{
myCopy.Add(o);
}
}
Once this code has run, there's only one copy of every item in your list. But each of the items is in both of the lists. If you change, clear or otherwise manipulate yourCopy, myCopy knows nothing about it. You cannot "destroy" any of the objects that are within the list my clearing yourCopy - all you do is release your own reference to them.
Assuming you are using WPF:
List<User> doesn't fire any event that the UI will recognise to refresh itself. If you use ObservableCollection<User> instead, your code will work.
The key difference is that ObservableCollection implements INotifyCollectionChanged, which allows the UI to recognise that the content of the collection has changed, and thus refresh the content of the ComboBox.
(Note that this does not work in WinForms. In WinForms you can set the DataSource property of the control, but the same ObservableCollection trick does not work here.)
When you set a collection reference to ItemsControl, all the combo gets is a reference, that it knows is enumerable.
It will enumerate the reference and display the items. Whether it does a deep copy or shallow copy is irrelevant, all it has is a reference (memory address effectively).
If you change your collection in some way, the combo has no way of knowing unless you tell it somehow. The reference (address) hasn't changed, everything looks the same to the combo. You seem to be thinking that the object is somehow "live" and the combo can watch the memory changing or something? This isn't the case. All it has is a reference that it can enumerate over. The contents can change but without some trigger the combo doesn't know that, and so will sit doing nothing.
ObservableCollection is designed to overcome this. It implements INotifyCollectionChanged that fires events when it changes, so the Combo knows that it must update its display.

Auto-Update a listbox bind with a Database without refreshing the page in Silverlight ...?

I have a list box in silvelight bind with a Observable collection wit is filled form a database , like this :
var item = new AllCommentsPerMaterialCategoryItem { CommenterName = name,
Text = project.Text, ID = project.ID, NoLike = (int)project.NoLike, SID = l, PID = i,
VID = (int)project.MID, Date = project.date.ToString() };
=_viewModel.AllCommentsPerMaterialCategoryItem.Add(item);= }
ObservableCollection<AllCommentsPerMaterialCategoryItem> GenreList =
_viewModel.AllCommentsPerMaterialCategoryItem; //result from a call to a WCF
service which returns the observable collection
GenreList1 = _viewModel.AllCommentsPerMaterialCategoryItem;
mainMenuList.ItemsSource = GenreList;
I want whenever someone adds a new item in the database to be automatically filled(updated-refreshed) in list box and also whenever I add a new item to be added to the list
I read that the Observable collection would internally implement "INoifyProbertyChange"
but its not working , also I'm not sure if that would do these requirements or not , i guess it would the second only.
any ideas please ?
ObservableCollection does indeed internally implement INotifyPropertyChanged. However, it's not going to go to the database for you. You need to either refresh from the database on a timer or set up a duplex service to notify you, and then add/remove items from the ObservableCollection as necessary.
The best usage of this would be to create a property returning an instance of the ObservableCollection and bind to it, then simply modify the collection as things change. If you're going to swap out the entire collection you'll need to make sure that the property itself notifies of the property changed as there's an important distinction between the ObservableCollection notifying the UI that its collection has changed and notifying that the variable that stored it changed to a different collection. The latter there doesn't happen automatically.

Resources