WPF ComboBox Refresh After Collection Update - c#

I am using ComboBox in wpf as below and want to update ComboBox behind the seen if i update collection :-
<xmlns:dataProvider="clr-namespace:DataProvider"
<UserControl.Resources>
<dataProvider:BackOfficeDataProvider x:Key="DataProvider"/>
</UserControl.Resources>
<ComboBox x:Name="groupGroupNameCombo" HorizontalAlignment="Left" Margin="368,123,0,0" VerticalAlignment="Top" Width="226" Height="31" SelectionChanged="groupGroupNameCombo_SelectionChanged" DisplayMemberPath="GroupName" SelectedItem="{Binding ParentID, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding GroupParentList, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, Source={StaticResource DataProvider}}" IsSynchronizedWithCurrentItem="True">
</ComboBox>
Class BackOfficeDataProvider {
public static ObservableCollection<Categories> groupParentList = null;
public virtual ObservableCollection<Categories> GroupParentList
{
get { return groupParentList ; }
set
{
groupParentList = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("GroupParentList");
}
}
public void loadComboListData();
{
GroupParentList = (ObservableCollection<Categories>) //fetching data from database using NHibernate directly getting list ;
}
}
my front end class which has refresh button :-
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
new BackOfficeDataProvider().loadComboListData();
}
when application load that time i can see the item in combobox but when i click on Refresh button that time it load updated data from database but not updating combobox untill i use below code
groupGroupNameCombo.ItemsSource = null;
groupGroupNameCombo.ItemSource = GroupParentList ;
Its a manually thing i have to do always to refresh combobox, how can i make it automatic like if i update collection then it should update combobox at the same time and i don't need to use above workaround.

I think this may have something to do with breaking the coupling between the combobox and the ObservableCollection when doing this:
GroupParentList = //fetching data from database;
Try this instead:
var dbCategories = // Get data from DB
GroupParentList.Clear();
foreach (var item in dbData)
GroupParentList.Add(item);
The point is to update the items in the collection, not the collection itself.
Also, try defining your collection like this, it should'nt have to be instantiated more than once (i.e no setter):
public static ObservableCollection<Categories> groupParentList = null;
public virtual ObservableCollection<Categories> GroupParentList
{
get
{
if (groupParentList == null)
groupParentList = new ObservableCollection<Categories>();
return groupParentList;
}
}

Hogler is right, your approach of assigning a new ObservableCollection object to the binding property will break how binding works. For ObservableCollection to work, you will need to modify the items in the collection itself, ObservableCollection is responsible of publishing list changes to the binding target. When you assign a new collection to the binding target, the list will not get refresh unless you published PropertyChanged event again to register this new binding source.
In your later comment you did state that you only instantiate ObservableCollection once only, which is not obvious from your posted code. It appears to me that the reason why it doesn't work is because you assign a new collection to the "GroupParentList" each time you run "loadComboListData".

Try this ..
once You are done getting data from your database in groupParentList , Add below Line, it will work as below :-
GroupParentList = new ObservableCollection<Categories>(groupParentList )

Related

UWP - Refresh ListView from a running async method

I have written a method which sends an UDP broadcast und recieves the results in a loop which are instantly written in to a List<String>. Since I'm using ReceiveAsync() it is running asynchronous.
My question is: how can I refresh the ListView in my UI each time the loop adds a string to my list. So I want the results to be displayed instantly on the screen as they appear in the list.
Code
do
{
UdpReceiveResult result = await sclient.ReceiveAsync();
ipList.Add(result.RemoteEndPoint.Address.ToString());
// after this step I want the ListView to get refreshed
} while (sclient.Available != 0);
XAML ListView Code
<ListView x:Name="lbIps" HorizontalAlignment="Left" Height="174"
Margin="450,151,0,0" VerticalAlignment="Top" Width="298" />
Code behind XAML
public async void btnBroadcast_Click(object sender, RoutedEventArgs e)
{
await ND2.run();
lbIps1.ItemsSource = ND2.ipList;
}
First of all you should bing the data to the ListView using data binding:
<ListView x:Name="lbIps" ItemsSource="{x:Bind IpList}" ... />
Now you have to actually create such property in the code-behind:
ObservableCollection<string> IpList => ND2.ipList;
Finally, change the type of ipList to ObservableCollection<string>. You no longer have to set the ItemsSource manually inside the btnBroadcast_Click method as it is bound directly to the ND2.ipList. Also, thanks to the fact that it is a ObservableCollection<string> any new items added will automatically be reflected in the UI.
**Note: ** Make sure, you don't create a new instance of the ipList, because the ListView would stay bound to the original instance. I presume ipList is a field or a property:
public ObservableCollection<string> ipLIst {get;} = new ObservableCollection<string>();
Now the property is initialized at the beginning and will not change. You can use Clear() method to remove all elements in the collection if necessary instead of setting a new instance.

How to resolve a null binding on a ComboBox?

Overview:
I've set a binding on a ComoboBox to a List property. But when I run the application there is no data populated in the combo box.
Debug steps:
I checked the output window for binding errors which tells me that the data source might be null.
I then set a breakpoint on the setter of the QueryList property. This shows that the list count is 0. It seems the call to init executes after the setter is called on the property.
My thoughts are that the list is being initialized after the setter is called. Meaning that the binding will be null at that stage the binding is called on the combo box.
Question:
How can I call the Init method for my list prior to the QueryList setter being called?
Code snippet:
Code behind -
//The binding property for the combo box
private List<string> _queryList;
public List<string> QueryList
{
get
{
return this._queryList;
}
set
{
this._queryList = value;
}
}
public MainWindow()
{
InitializeComponent();
// Establish the Login control
Ctrl = new CrmLogin();
QueryList = new List<string>();
InitQueryList();
}
//Call to init the list data
private void InitQueryList()
{
_queryList.Add("Query queues with unapproved email routers");
_queryList.Add("Query queues with emails in pending send status");
}
Combobox binding setup -
<ComboBox HorizontalAlignment="Left" ItemsSource="{Binding QueryList}" Grid.Column="1" x:Name="queryComboBox" Grid.Row="0" VerticalAlignment="Bottom" Width="300" Visibility="Collapsed" Text="Select a query"/>
You forget to set your DataContext :
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
// Establish the Login control
Ctrl = new CrmLogin();
QueryList = new List<string>();
InitQueryList();
}
Try this :
public MainWindow()
{
// Establish the Login control
QueryList = new List<string>();
InitQueryList();
InitializeComponent();
Ctrl = new CrmLogin();
}
Firstly if you are using Code behind method MVC then you would need to update the datasource using
comboBox1.DataSource = QueryList;
Else if you are using the standard MVVM format then you would need to use
INotifyPropertyChanged
Else You will need to use
ObservableCollection
This happens because on initialization null value of your _querylist gets binded initially. Now when your querylist gets updated this doesnt get reflected in your view as View doesnt get any notification or event stating that a change has been made to the viewmodel(your binded item)

MVVM Dynamic DataGrid Sorting Filtering

I have a DataGrid that gets its data updated every few seconds via a Thread. The DataGrid needs to offer Column Header sorting, grouping and filtering.
I currently have a DataGrid bound to a ICollectionView and the source of the ICollectionView is an ObservableCollection. Which seems to be the good way to do it from what I read on other threads.
The sort-ing "works" but it gets lost when the ICollectionView.Source gets updated following an update of the ObservableCollection. I have tried saving the SortDescriptions before the update and re-add it to the ICollectionView after the update is done. But it's the same result.
May someone point me to what I'm missing?
Edit Here's some code...
View (XAML)
<DataGrid ItemsSource="{Binding CollectionView, Source={StaticResource ViewModel}}>
ViewModel
public ICollectionView CollectionView
{
get
{
collectionViewSource.Source = dataColl;
if (SortDescriptions != null)
{
foreach (SortDescription sd in SortDescriptions)
{
collectionViewSource.View.SortDescriptions.Add(sd);
}
}
collectionViewSource.View.Refresh();
return collectionViewSource.View;
}
}
public ObservableCollection<SomeObject> DataColl
{
get { return dataColl; }
private set
{
this.dataColl= value;
OnPropertyChanged("CollectionView");
}
}
Following is the method that updates the data every few seconds...
private void UpdateData()
{
while (true)
{
System.Threading.Thread.Sleep(mDataRefreshRate);
// SortDescriptions is a Property of the ViewModel class.
SortDescriptions = collectionViewSource.View.SortDescriptions;
ObservableCollection<SomeObject> wDataColl
= new ObservableCollection<SomeObject>();
//... Irrelevant code that puts the data in wDataColl ...
DataColl= wDataColl;
}
}
[YourObservableCollection].ViewHandler.View.Filter
+= new FilterEventHandler(myFilterHandler);
private void myFilterHandler(object sender, FilterEventArgs e)
{
}
Can be used to directly add your filter handler and you can do the same with SortDescriptions to Add/Remove
[YourObservableCollection].ViewHandler.View.SortDescriptions.Add(mySortDescription);
If you are doing allot of sorting and filtering best to create own class encapsulating a CollectionViewSource and implement adding and removing SortDescriptions and Filtering etc
When you say:
The sort "works" but it gets lost when the ICollectionView.Source gets
updated following an update of the ObservableCollection
What do you mean by update? you mean you are changing the Source? Rather than adding/removing items from the collection?
EDIT based on your XAML example you added:
<DataGrid ItemsSource="{Binding CollectionView, Source={StaticResource ViewModel}}>
You are binding itemsource to the CollectionViewSource where you should bind the datacontext to it:
Example:
<Page.Resources>
<CollectionViewSource x:Key="myViewSource"
Source="{Binding CollectionView, Source={StaticResource ViewModel}}"
/>
</Page.Resources>
In page:
<Grid DataContext="{StaticResource myViewSource}">
<DataGrid x:Name="myGrid" ItemsSource="{Binding}"...
Or something along those lines
EDIT again didnt see code scroll down :p
ObservableCollection<SomeObject> wDataColl= new ObservableCollection<SomeObject>();
You create new instance every time of collection lol this is main problem
Also:
public ICollectionView CollectionView
{
get
{
collectionViewSource.Source = dataColl;
if (SortDescriptions != null)
{
foreach (SortDescription sd in SortDescriptions)
{
collectionViewSource.View.SortDescriptions.Add(sd);
}
}
collectionViewSource.View.Refresh();
return collectionViewSource.View;
}
}
Here where you return collection you are setting the Source and adding the SortDescriptions and also refreshing the view every time, you only need set these values once
You would only call refresh on the View if you add/remove SortDescriptions
I think you should get to grips with basics of CollectionViewSource
The problem is that you swap out the entire ObservableCollection every time you add new data.
ObservableCollection<SomeObject> wDataColl= new ObservableCollection<SomeObject>();
... Unrelevant code that puts the data in wDataColl ...
DataColl= wDataColl;
Make sure to use Add to the existing collection instead (perhaps after using Clear() first if that is necessary)... If you still have problems after that please comment and i will try to help.
Also, try to avoid using the Refresh() as it rebuilds the entire view and is unnecessarily expensive. If you do sorting, adding, removing etc. the correct way use of Refresh() isn't needed.

silverlight Binding from VM collection to View not working need some help

I know I am missing something here and I could use a pointer. Within a project I have an expander control when this control is clicked it makes a RIA call to a POCO within my project to retreive a second set of data. I am using the SimpleMVVM toolkit here so please let me know if I need to expand on any additional areas.
Within the xaml the expander is laid out as
<toolkit:Expander Header="Name" Style="{StaticResource DetailExpanderSytle}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<ei:CallMethodAction
TargetObject="{Binding Source={StaticResource vm}}"
MethodName="showWarrantNameDetail"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel Orientation="Vertical">
<sdk:DataGrid AutoGenerateColumns="true" ItemsSource="{Binding NameResult}" AlternatingRowBackground="Gainsboro" HorizontalAlignment="Stretch" MaxHeight="200">
</sdk:DataGrid>
<local:NameContainer DataContext="{Binding}" />
</StackPanel>
</toolkit:Expander>
I am using the expression Dll coupled with Simple MVVM to get at the methods in the view model vs commands.
Within the view model I have the following code
public void showWarrantNameDetail()
{
//set flags
IsBusy = true;
CanDo = false;
EntityQuery<WarrantNameDataView> query = App.cdContext.GetWarrantNameDataViewsQuery().Where(a => a.PrimaryObjectId == Convert.ToInt32(RecID));
Action<LoadOperation<WarrantNameDataView>> completeProcessing = delegate(LoadOperation<WarrantNameDataView> loadOp)
{
if (!loadOp.HasError)
{
processWarrantNames(loadOp.Entities);
}
else
{
Exception error = loadOp.Error;
}
};
LoadOperation<WarrantNameDataView> loadOperation = App.cdContext.Load(query, completeProcessing, false);
}
private void processWarrantNames(IEnumerable<WarrantNameDataView> entities)
{
ObservableCollection<WarrantNameDataView> NameResult = new ObservableCollection<WarrantNameDataView>(entities);
//we're done
IsBusy = false;
CanDo = true;
}
When I set a break on the processWarrantName I can see the NameResult is set to X number of returns. However within the view the datagrid does not get populated with anything?
Can anyone help me understand what I need to do with the bindings to get the gridview to populate? Other areas of the form which are bound to other collections show data so I know I have the data context of the view set correctly. I've tried both Data context as well as Items Source and no return?
When I set a break on the code the collection is returned as follows so I can see that data is being returned. Any suggestions on what I am missing I would greatly appreciate it.
With regards to the page datacontext I am setting it in the code behind as follows:
var WarrantDetailViewModel = ((ViewModelLocator)App.Current.Resources["Locator"]).WarrantDetailViewModel;
this.DataContext = WarrantDetailViewModel;
this.Resources.Add("vm", WarrantDetailViewModel);
Thanks in advance for any suggestions.
Make ObservableCollection<WarrantNameDataView> NameResult a public property of your ViewModel class. Your view will not be able to bind to something that has a private method scope (or public method scope, or private member scope).
//declaration
public ObservableCollection<WarrantNameDataView> NameResult { get; set }
//in the ViewModel constructor do this
NameResult = new ObservableCollection<WarrantNameDataView>();
//then replace the original line in your method with:
//EDIT: ObservableCollection has no AddRange. Either loop through
//entities and add them to the collection or see OP's answer.
//NameResult.AddRange(entities);
If processWarrantNames gets called more than once, you might need to call NameResult.Clear() before calling AddRange() adding to the collection.
Phil was correct in setting the property to public. One note I'll add is there is no AddRange property in SL or ObservableCollection class that I could find. I was able to assign the entities to the OC using the following code
private ObservableCollection<WarrantNameDataView> warrantNameResult;
public ObservableCollection<WarrantNameDataView> WarrantNameResult
{
get { return warrantNameResult; }
set
{
warrantNameResult = value;
NotifyPropertyChanged(vm => vm.WarrantNameResult);
}
}
and then within the return method
WarrantNameResult = new ObservableCollection<WarrantNameDataView>(entities);
This worked and passed to the UI the collection of data.

ListBox presenting only one item

I have a ListBox and a class with strings. Each time that a user clicks add button in the application, I create a new instance of the class and add it to the list which is binded to the ListBox. The first time I click the add button, the list box shows the first item, but the next time it doesn't show two items.
XAML - this is the ListBox:
<ListBox Name="ListBox_BinsRegion" Height="181" Margin="233,16,6,94" Width="253" Background="Transparent" BorderThickness="1" BorderBrush="Black" ScrollViewer.VerticalScrollBarVisibility="Auto" ItemsSource="{Binding}"/>
The code behind:
List<Class_ListViewItem> List_ListBoxItems = new List<Class_ListViewItem>();
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
Class_ListViewItem item = new Class_ListViewItem();
item.WH = this.comboBox_WareHouseBinsRegionDefinition.SelectedItem.ToString();
item.XXFrom = textBox_XXFrom.Text;
item.XXTo = textBox_XXTo.Text;
item.YYFrom = textBox_YYFrom.Text;
item.YYTo = textBox_YYTO.Text;
item.Z = textBox_ZFrom.Text;
List_ListBoxItems.Add(item);
ListBox_BinsRegion.DataContext = List_ListBoxItems;
}
Where is my mistake?
WPF does not know when your collection is changing. The problem is here:
List<Class_ListViewItem> List_ListBoxItems = new List<Class_ListViewItem>();
you need to change the list to
ObservableCollection<Class_ListViewItem> List_ListBoxItems = new ObservableCollection<Class_ListViewItem>();
ObservableCollection (System.Collections.ObjectModel) throws an event when the collection is changed, so that WPF can update the listbox.
Also, you can remove the following line, or move it to the constructor of your control.
ListBox_BinsRegion.DataContext = List_ListBoxItems;
You should not change the DataContext of the control, instead set the binding to theList_ListBoxItems and make it a public property, and use an ObservableCollection or BindableCollection instead of list
When you assign the DataContext the second time, it doesn't technically change. This is because you are assigning it to the same collection. You should do something like this instead:
ObservableCollection<Class_ListViewItem> List_ListBoxItems = new ObservableCollection<Class_ListViewItem>();
public YourControl() {
InitializeComponent();
ListBox_BinsRegion.DataContext = List_ListBoxItems;
}
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
Class_ListViewItem item = new Class_ListViewItem();
item.WH = this.comboBox_WareHouseBinsRegionDefinition.SelectedItem.ToString();
item.XXFrom = textBox_XXFrom.Text;
item.XXTo = textBox_XXTo.Text;
item.YYFrom = textBox_YYFrom.Text;
item.YYTo = textBox_YYTO.Text;
item.Z = textBox_ZFrom.Text;
List_ListBoxItems.Add(item);
}
Use an ObservableCollection<> rather than a List<>. This will update the binding automatically, with no need for the following line (which can be kind of slow)
ListBox_BinsRegion.DataContext = List_ListBoxItems;
You could either do what everyone else already suggested (using an ObservableCollection instead of the List) - or you could query the dependency property which is bound and find the corresponding Binding and refresh it manually.
I'd go for the ObservableCollection :)

Categories

Resources