I have a window in my application with following resources:
<Window.Resources>
<ResourceDictionary>
<Data:IssueRecords x:Key="DataSource"/>
<CollectionViewSource x:Key="DataCollection" Source="{StaticResource DataSource}"
Filter="CollectionViewSource_Filter">
</CollectionViewSource>
</ResourceDictionary>
</Window.Resources>
There is a standard event handler - a method, called CollectionViewSource_Filter and DataGrid, to apply filter to. After my window loads, everything works perfectly, including filters.
For applying filters, I call a ReloadGrid method...
private void ReloadGrid(object sender, RoutedEventArgs e)
{
CollectionViewSource.GetDefaultView(GridData.ItemsSource).Refresh();
}
But, when user does any action, which makes changes to my database (delete, modify or create new), I need to reload those data sources, so I call...
private void ReloadDataSources()
{
var dataSource = this.FindResource("DataSource") as IStockRecords;
dataSource.ReloadData();
var dataCollection = this.FindResource("DataCollection") as CollectionViewSource;
dataCollection = new CollectionViewSource() { Source = dataSource };
dataCollection.Filter += new FilterEventHandler(CollectionViewSource_Filter);
Binding binding = new Binding() { Source = dataCollection };
BindingOperations.SetBinding(GridData, DataGrid.ItemsSourceProperty, binding);
}
I think, I do everything, what is needed to read actual data from database and reload the datasources in my window. But when I use any filter, after I call ReloadDataSources(), the filter event is not being used anymore. I debugged a source code and Refresh method doesn't invoke CollectionViewSource_Filter, even when I set FilterEventHandler...
Am i missing anything?
Thanks, JiKra
You need to reset your default view after you modify the data source.
CollectionViewSource.GetDefaultView(GridData.ItemsSource).Refresh();
Try this....
private void ReloadDataSources()
{
var dataSource = this.FindResource("DataSource") as IStockRecords;
dataSource.ReloadData();
var dataCollection = this.FindResource("DataCollection") as CollectionViewSource;
// Remove the current event handler
dataCollection.Filter -= new FilterEventHandler(CollectionViewSource_Filter);
// Set your new data source
dataCollection = new CollectionViewSource() { Source = dataSource };
// Read your handler
dataCollection.Filter += new FilterEventHandler(CollectionViewSource_Filter);
// Now reset your filter
dataCollection .GetDefaultView(GridData.ItemsSource).Refresh();
Binding binding = new Binding() { Source = dataCollection };
BindingOperations.SetBinding(GridData, DataGrid.ItemsSourceProperty, binding);
}
OK, there seems to be a problem when I recreated CollectionViewSource object. So, the final version is...
private void ReloadDataSources()
{
var dataSource = this.FindResource("DataSource") as IStockRecords;
dataSource.ReloadData();
var dataCollection = this.FindResource("DataCollection") as CollectionViewSource;
//here I just had to refresh collection's view, not to create a new one
dataCollection.View.Refresh();
Binding binding = new Binding() { Source = dataCollection };
BindingOperations.SetBinding(GridData, DataGrid.ItemsSourceProperty, binding);
}
Thank you both for your effort...
JiKra
Related
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'm new to WPF and am using the Syncfusion Framework. I want to use the DataTreeControl to display a hierarchy of data which will be loaded and updated in a reoccuring interval. But for some reason it doesn't display the data.
Here's a snipped from my MainWindow.xaml
<syncfusion:TabItemExt Name="_tabItemTipps" Header="Tipps">
<syncfusion:GridTreeControl Name="_treeGrid"
BorderBrush="LightGray"
BorderThickness="0,0.5,0,0"
EnableHotRowMarker="False"
EnableNodeSelection="True"
ExpandStateAtStartUp="AllNodesExpanded"
ReadOnly="True"
SupportNodeImages="True"
VisualStyle="Metro"
ItemsSource="SoccerMarkets"
>
<!-- Code for GridTreeControl Columns -->
<syncfusion:GridTreeControl.Columns>
<syncfusion:GridTreeColumn HeaderText="Nation" MappingName="{Binding RoughCat}"></syncfusion:GridTreeColumn>
</syncfusion:GridTreeControl.Columns>
</syncfusion:GridTreeControl>
This the snippet from MainWindow.xaml.cs where the DataContext is set:
public MainWindow()
{
DataContext = this;
InitializeComponent();
SkinStorage.SetVisualStyle(_tabControl, "Metro");
_settingsVM = new AppSettingsVM();
_txtBetdaqUser.DataContext = _settingsVM;
_chkSystemActive.DataContext = _settingsVM;
_chkInSimulationMode.DataContext = _settingsVM;
_mechanic = new TippMechanic(_settingsVM);
_soccerMarketsVM = new SoccerMarketVM();
Task[] tasks = new Task[1];
tasks[0] = Task.Factory.StartNew(async () => await _mechanic.Init());// _mechanic.Init();
Task.WaitAll(tasks);
_soccerMarketsVM.SoccerMarkets = _mechanic.SoccerMarketManager.SoccerMarkets;
_treeGrid.DataContext = _soccerMarketsVM.SoccerMarkets;
}
My ViewModel (_soccerMarketsVM) is defined this way:
class SoccerMarketVM : ObservableObject
{
private ObservableCollection<SoccerMarket> _soccerMarkets;
public ObservableCollection<SoccerMarket> SoccerMarkets
{
get { return _soccerMarkets; }
set
{
if(_soccerMarkets != null)
_soccerMarkets.CollectionChanged -= _soccerMarkets_CollectionChanged;
_soccerMarkets = value;
_soccerMarkets.CollectionChanged += _soccerMarkets_CollectionChanged;
}
}
public SoccerMarketVM()
{
//_soccerMarkets = new ObservableCollection<SoccerMarket>();
//_soccerMarkets.CollectionChanged += _soccerMarkets_CollectionChanged;
}
void _soccerMarkets_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine(e.Action.ToString());
}
}
The Events for CollectionChanged are fired and I get the Console.Writeline output.
Does anyone see's something wrong here?
In GridTreeControl, you can populate the data using different ways. In your code snippet, ItemsSource defined without specifying the Binding keyword and the MappingName is defined with Binding keyword. But for itemssource, you need to specify binding and for mapping name, you can directly assign property name without specifying binding. Please refer the below UG link of data population in GridTreeControl,
Link:
http://help.syncfusion.com/ug/wpf/index.html#!Documents/addingthegridtreecontroltoawpfapplication.htm
Elavarasan M – Syncfusion Software.
I would like to bind a DataGrid*Column (in this particular case, a DataGridTextBox) to its data in the code-behind. This is because, depending on a CheckBox's IsClicked property, the Column needs to bind to different collections.
Solutions such as this one all point to the following sort of code:
var binding = new Binding("X");
XColumn.Binding = binding;
Now, I've made use of this sort of code in other parts of my program with success, just not with a DataGrid*Column. With the column, however, this is not working as expected, since in fact all the rows of the column are presenting the X-value of the first element of the collection. This is confirmed when I edit any of the cells and all of them are altered, meaning they are all bound to the same single element of the collection, not to the collection as a whole.
Here is the relevant code:
//This is called whenever the CheckBox EqualToResults is clicked
void ControlBindings()
{
//only showing for (.IsChecked == true), but the other case is similar
//and presents the same problems
if (EqualToResults.IsChecked == true)
{
var cable = DataContext as NCable;
//This is the DataGrid object
Coordinates.ItemsSource = cable;
var binding = new Binding("X");
binding.Source = cable.Points;
//XColumn is the DataGridTextColumn
XColumn.Binding = binding;
}
}
Should it be relevant, here's the relevant code for the NCable class.
public class NCable : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<NPoint> Points;
public static DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(ICollectionView), typeof(NCable));
public ICollectionView IPointCollection
{
get { return (ICollectionView)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
public NCable(string cableName)
{
Points = new ObservableCollection<NPoint>();
for (int i = 0; i < 11; i++)
Points.Add(new NPoint(1,1));
IPointCollection = CollectionViewSource.GetDefaultView(Points);
}
}
EDIT 13/05: I've seen somewhere that one must also set the ItemsSource of the DataGrid in this case, so I've done that as well (edited the original code), but still to no avail. The entire column is still bound to the first element of the collection.
Figured it out. In this case, the DataGrid.ItemsSource must be defined (as per the edit in the oP), but the binding.Source must be left undefined. Therefore, the functional code-behind is
void ControlBindings()
{
if (EqualToResults.IsChecked == true)
{
var cable = DataContext as NCable;
Coordinates.ItemsSource = cable;
var binding = new Binding("X");
//REMOVE binding.Source = cable.Points;
XColumn.Binding = binding;
}
}
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 :)
I have a Datagrid which show data from a database.
each time i load the data from my data vase the old data is earsed and the is replaced with the new data.
What i want is for the pervious row to remain and the new data to be appened to the end of the datagrid.
my code is as shown below :
public MainPage()
{
InitializeComponent();
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "Year", Binding = new System.Windows.Data.Binding("year") });
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "One", Binding = new System.Windows.Data.Binding("One") });
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "Two", Binding = new System.Windows.Data.Binding("Two") });
dataGrid1.Columns.Add(new DataGridTextColumn { Header = "Three", Binding = new System.Windows.Data.Binding("Three") });
}
void client_DoWorkCompleted(object sender, DoWorkCompletedEventArgs e)
{
dataGrid1.ItemsSource = e.Result;
}
how do i append the new data from the database rather than overwriting the data ?? .
One very simple and no-frills way to do it (because you don't appear to be using a regular MVVM approach), is to create a new list (IEnumerable) which unions both the old list and the new list, then assign it back to the ItemsSource property:
List<MyObjects> abc = new List<MyObjects>(dataGrid2.ItemsSource).Union(e.Result);
dataGrid2.ItemsSource = abc;
I've broken this on to two lines to make it easier to understand.
If you were using a ViewModel, then the property that is bound to the DataGrid would be a List<>, and then you can just do an AddRange(e.Result) to that property, and due to the beauty of data binding (and notifying) it will automatically show up in your datagrid.