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.
Related
I have a form that has a dynamic amount of datagrids that are brought in programmatically each one on a new tabpage.
My problem is that I need to change the Header of each column. I have tried doing it through a method
DataGridForSupplier.Columns[0].Header = "123";
but that keeps crashing with an error:
Index was out of range. Must be non-negative and less than the size of the collection
Turns out the problem is that the grid wasn't finished loading. So after waiting for all tabpage to load and add data to all the grids , even then the code
DataGridForSupplier.Columns[0].Header = "123";
would still crash. If the tabs are left to load on their own with no header tampering then the datagrid shows fine.
I would just LOVE to do this in XAML problem is that seeing that I don't know how many grids will load at run time I tried doing this at the back. So I'm open to any solution at this point. I tried finding a solution that would incorporate something that would 'theme' all the datagrids. Luckily all the datagrids headers will repeat across all tabs. So header 1 on tabpage 1 - 10 will be the same. Header 2 on tabpage 1 - 10 will be the same
Something like
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding DataContext.HeaderNameText, RelativeSource=>> RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataGridTemplateColumn.Header>
but this needs to repeat for every Grid. This seems to escape me at the moment.
Any help would be welcome.
A rather lengthy answer, but this solution does not require any additional libraries, 3rd party tools, etc. You can expand it as you want later such as for adding hooks to mouse-move/over/drag/drop/focus, etc. First the premise on subclassing which I found out early in my learning WPF. You can not subclass a xaml file, but can by a .cs code file. In this case, I subclassed the DataGrid to MyDataGrid. Next, I created an interface for a known control type to ensure contact of given functions/methods/properties. I have stripped this version down to cover just what you need to get.
The interface below is just to expose any class using this interface MUST HAVE A METHOD called MyDataGridItemsChanged and expects a parameter of MyDataGrid.. easy enough
public interface IMyDataGridSource
{
void MyDataGridItemsChanged(MyDataGrid mdg);
}
Now, declaring in-code a MyDataGrid derived from DataGrid. In this class, I am adding a private property of type IMyDataGridSource to grab at run-time after datagrids are built and bound.
public class MyDataGrid : DataGrid
{
// place-holder to keep if so needed to expand later
IMyDataGridSource boundToObject;
public MyDataGrid()
{
// Force this class to trigger itself after the control is completely loaded,
// bound to whatever control and is ready to go
Loaded += MyDataGrid_Loaded;
}
private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
// when the datacontext binding is assigned or updated, see if it is based on
// the IMyDataGridSource object. If so, try to type-cast it and save into the private property
// in case you want to add other hooks to it directly, such as mouseClick, grid row changed, etc...
boundToObject = DataContext as IMyDataGridSource;
}
// OVERRIDE the DataGrid base class when items changed and the ItemsSource
// list/binding has been updated with a new set of records
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
// do whatever default behavior
base.OnItemsChanged(e);
// if the list is NOT bound to the data context of the IMyDataGridSource, get out
if (boundToObject == null)
return;
// the bound data context IS of expected type... call method to rebuild column headers
// since the "boundToObject" is known to be of IMyDataGridSource,
// we KNOW it has the method... Call it and pass this (MyDataGrid) to it
boundToObject.MyDataGridItemsChanged(this);
}
}
Next into your form where you put the data grid. You will need to add an "xmlns" reference to your project so you can add a "MyDataGrid" instead of just "DataGrid". In my case, my application is called "StackHelp" as this is where I do a variety of tests from other answers offered. The "xmlns:myApp" is just making an ALIAS "myApp" to the designer to it has access to the classes within my application. Then, I can add
<Window x:Class="StackHelp.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:StackHelp"
Title="Main Window" Height="700" Width="900">
<StackPanel>
<!-- adding button to the main window to show forced updated list only -->
<Button Content="Refresh Data" Width="100"
HorizontalAlignment="Left" Click="Button_Click" />
<myApp:MyDataGrid
ItemsSource="{Binding ItemsCollection, NotifyOnSourceUpdated=True}"
AutoGenerateColumns="True" />
</StackPanel>
</Window>
Now, into the MyMainWindow.cs code-behind
namespace StackHelp
{
public partial class MyMainWindow : Window
{
// you would have your own view model that all bindings really go to
MyViewModel VM;
public MyMainWindow()
{
// Create instance of the view model and set the window binding
// to this public object's DataContext
VM = new MyViewModel();
DataContext = VM;
// Now, draw the window and controls
InitializeComponent();
}
// for the form button, just to force a refresh of the data.
// you would obviously have your own method of querying data and refreshing.
// I am not obviously doing that, but you have your own way to do it.
private void Button_Click(object sender, RoutedEventArgs e)
{
// call my viewmodel object to refresh the data from whatever
// data origin .. sql, text, import, whatever
VM.Button_Refresh();
}
}
}
Finally to my sample ViewModel which incorporates the IMyDataGridSource
public class MyViewModel : IMyDataGridSource, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public ObservableCollection<OneItem> ItemsCollection { get; set; }
= new ObservableCollection<OneItem>();
public void Button_Refresh()
{
ItemsCollection = new ObservableCollection<OneItem>
{
new OneItem{ DayName = "Sunday", DayOfWeek = 0},
new OneItem{ DayName = "Monday", DayOfWeek = 1},
new OneItem{ DayName = "Tuesday", DayOfWeek = 2},
new OneItem{ DayName = "Wednesday", DayOfWeek = 3},
new OneItem{ DayName = "Thursday", DayOfWeek = 4},
new OneItem{ DayName = "Friday", DayOfWeek = 5 },
new OneItem{ DayName = "Saturday", DayOfWeek = 6 }
};
RaisePropertyChanged("ItemsCollection");
}
// THIS is the magic hook exposed that will allow you to rebuild your
// grid column headers
public void MyDataGridItemsChanged(MyDataGrid mdg)
{
// if null or no column count, get out.
// column count will get set to zero if no previously set grid
// OR when the items grid is cleared out. don't crash if no columns
if (mdg == null)
return;
mdg.Columns[0].Header = "123";
}
}
Now, taking this a step further. I don't know how you manage your view models and you may have multiple grids in your forms and such. You could create the above MyViewModel class as a smaller subset such as MyDataGridManager class. So each datagrid is bound to its own MyDataGridManager instance. It has its own querying / populating list for the grid, handling its own rebuild column headers, mouse clicks (if you wanted to expand), record change selected, etc.
Hope this helps you some. Again, this does not require any other 3rd party libraries and you can extend as you need. I have personally done this and more to the data grid and several other controls for certain specific pattern handling.
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)
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 )
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.
This little bit of code will help me describe my problem:
public class Car
{
...
}
public class CarQueue : ObservableCollection<Car>
{
public IEnumerable Brands
{
get { return (from Car c in this.Items select c.Brand).Distinct(); }
}
}
Ok now I have an instance of CarQueue class bound to a DataGrid. When I add a Car object to the queue the datagrid updates fine by itself, but I also have a listbox bound to the 'Brands' property which doesn't update. Here is a simple sequence of code to explain:
CarQueue cq = new CarQueue();
DataGrid1.ItemsSource = cq;
ListBox1.ItemsSource = cq.Brands; // all above done during window load
...
Car c;
cq.Add(c); // datagrid updates, but not listbox
Does the listbox not update because it is bound to a property with dynamic LINQ query?
One other thing I tried was inheriting INotifyPropertyChanged and adding a new event handler to the CollectionChanged event (in my CarQueue constructor):
this.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CarQueue_CollectionChanged);
Then in the event handler:
void CarQueue_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("Brands"));
}
This didn't work either. So does anyone know what the problem is? Thanks
There are a couple of problems here.
The Brands property is a sequence built on the fly by LINQ when it is asked for it. WPF only asks for it during the initial binding: it has no way of knowing that if it were to ask again it would get a different answer, so it doesn't. To get WPF to track changes to the Brands collection, you would need to expose Brands as a collection, and have INotifyCollectionChanged implemented on that collection -- for example by making Brands an ObservableCollection. One way to do this is using Bindable LINQ.
As an alternative, your second approach, of raising a PropertyChanged event for Brands, can be made to work. However, in order for this to work, you have to bind ItemsSource to Brands. (At the moment, you are assigning it, which means WPF forgets where the collection came from and just keeps its private copy of the values.) To do this, either use the {Binding} markup extension in XAML:
<ListBox ItemsSource="{Binding Brands}" /> <!-- assumes DataContext is cq -->
or use BindingOperations.SetBinding:
BindingOperations.SetBinding(ListBox1, ListBox.ItemsSourceProperty,
new Binding("Brands") { Source = cq });