I have a listview in WPF and C# that uses an ObservableCollection to populate the data. When a certain event is fired, I call a function to insert a new row into the collection. I then need to access the controls in that row.
The problem i am having is that after i insert the row, and I try to loop through the listview rows, this row is displaying as null in my loop.
if (addNewNote)
{
_resultsCollection.Insert(0, new ResultsData
{
Notes = "Data to Insert",
// ... rest of fields
});
} // end if (addNewNote)
for (int currRowIndex = 0; currRowIndex < this.ResultsList.Items.Count; currRowIndex++)
{
System.Windows.Controls.ListViewItem currRow =
(System.Windows.Controls.ListViewItem)this.ResultsList.ItemContainerGenerator.ContainerFromIndex(currRowIndex);
if (currRow != null)
{
System.Windows.Controls.TextBox tb = FindByName("EditNotesTextBox", currRow) as System.Windows.Controls.TextBox;
// do stuff with controls ...
}
}
Here currRow is always null, when the currRowIndex is zero.
This should not be the case, because i have just added it. Is it because I am trying to access it in the same function where I insert it, and the listview has not yet been updated? I am able to access every other row in the listview. Is there a better solution? Thanks!
In order to newly added item to be accessible first the container for that item (UI) needs to be generated by ItemContainerGenrator. The generation is done on the same (UI) thread, so you cannot access containers right after you have added an item.
You can subscribe to ResultsList.ItemContainerGenerator.StatusChanged event and in the event handler check if the status is GeneratorStatus.ContainersGenerated then you can obtain the container.
ResultsList.ItemContainerGenerator.StatusChanged += OnGeneratorStatusChanged;
...
private void OnGeneratorStatusChanged(object sender, EventArgs e)
{
if (MyListBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
// Access containers for newly added items
}
}
Please refer to this article by Dr.WPF that describes how it works in detail.
Related
I'm having some problems with ListBox databinding and immutability. I have a model that provides a List of some elements and a ViewModel that takes these elements and puts them to an ObservableCollection which is bound to the ListBox.
The elements, however, are not mutable so when they change - which happens when user changes ListBox's selection or in a few other scenarios - the model fires up an event and the ViewModel retrieves a new List of new elements instances and repopulates the ObservableCollection.
This approach works quite well - despite being obviously not optimal - when user interacts with the ListBox via mouse (clicking) but fails horribly when using keyboard (tab to focus current element and then using mouse arrows or further tabbing). For some reason the ActiveSchema gets always reset to the first element of the Schemas[*].
The ActiveSchema setter gets called for the schema user switched to, then for null, and finally for the first value again. For some reason the two last events don't happen when invoked via mouse.
PS: Full code can be found here
PPS: I know I should probably rework the model so it exposes ObservableCollection that mutates but there're reasons why trashing everything and creating it from scratch is just a bit more reliable.
//ListBox's Items source is bound to:
public ObservableCollection<IPowerSchema> Schemas { get; private set; }
//ListBox's Selected item is bound to:
public IPowerSchema ActiveSchema
{
get { return Schemas.FirstOrDefault(sch => sch.IsActive); }
set { if (value != null) { pwrManager.SetPowerSchema(value); } }
}
//When model changes:
private void Model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(IPowerManager.PowerSchemas))
{
updateCurrentSchemas();
}
}
private void updateCurrentSchemas()
{
Schemas.Clear();
var currSchemas = pwrManager.PowerSchemas;
currSchemas.ForEach(sch => Schemas.Add(sch));
RaisePropertyChangedEvent(nameof(ActiveSchema));
}
Can anyone give me what events to handle for syncfusion grid control.
In the context menu, if add record is chosen the the user must be able to add and then save it in the db
thanks
sun
If it is the ContextMenuStrip through which you bounded the context menu to the grid, you can handle the ItemClicked event of it and handle updates to the bounded data source. GridGroupingControl, by default, reflects the changes made in the underlying datasource provided the datasource supports notification on any changes(like IBindingList, IEnumerable, etc.,).
The following code illustrates this considering the bounded datasource as DataView.
// Event Handler
this.gridGroupingControl1.ContextMenuStrip.ItemClicked += new ToolStripItemClickedEventHandler(ContextMenuStrip_ItemClicked);
// Method Invoked
void ContextMenuStrip_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
DataTable dt = (this.gridGroupingControl1.DataSource as DataView).Table;
DataRow dr = dt.NewRow();
for (int i = 0; i < dt.Columns.Count; i++)
dr[i] = 0; //default value
dt.Rows.Add(dr);
}
For datasource types(like ArrayList), that don't raise notification on its own for any change on it, once the update to the underlying datasource have been handled, the following code can be handled to refresh the GridGroupingControl inorder to reflect the updated view.
this.gridGroupingControl1.Reinitialize();
I have an old Windows Forms application at work which I am converting to WPF. As part of this, there is a button which, when clicked, creates a brand new DataGridView and adds it to the page, binding it to data from a SQL query. This data is bever written back to the database, but a new column is added to the end of the data with a checkbox on it, and when the checkbox is changed the ID from the row is passed into another method along with the state of the checkbox.
In WPF, I have the dynamic grid creation working, and the data binding. Having found I couldn't directly add the column to the DataGrid itself, I've added it to the source DataSet table before binding. This works and whos all the data along with a checkbox for each row. However, I can't get an event to fire when the an individual checkbox is clicked.
I have managed to find some code at http://forums.silverlight.net/t/11547.aspx, which causes the row to commit whenever the checkbox is changed, as my code to pass the ID and bool value currently resides in an EditEnding event. Initially the UpdateSourceTrigger was added to each checkbox by looping over each row in the checkbox column during the "Loaded" event of the DataGrid, but this is now failing, and after some internet searching it seems to be because "Loaded" doesn't guarantee that the data has finished binding, and I'm finding that DataGrid.Items contains more items than there are containers in the grid at the point the event fires. Below is the code I'm trying to use to bind the UpdateTrigger, but at row 32 suddenly the ContainerFromIndex method returns null. There are 69 items in dgvNew.Items at that point.
dgvNew.Loaded += new RoutedEventHandler(delegate(object sender, RoutedEventArgs e)
{
for (var i = 0; i < dgvNew.Columns.Count; i++)
{
DataGridBoundColumn column = dgvNew.Columns[i] as DataGridBoundColumn;
if (column != null && column.Header.ToString() == "HasPermission")
{
for (var j = 0; j < dgvNew.Items.Count; j++)
{
DataGridRow row = (DataGridRow)dgvNew.ItemContainerGenerator.ContainerFromIndex(j);
UpdateSourceTriggerHelper.SetUpdateSourceTrigger(column.GetCellContent(row), true);
}
}
}
});
I've also tried wrapping the for loop up in an event handler for the ItemContainerGenerator.StatusChanged and checking that Status is ItemsGenerated, but that didn't help either.
Can anyone see where I'm going wrong, in code or in understanding?
I am developing WPF UserControl based on WPF DataGrid, to support dynamic column generation with our own business based context menu.
I've created Dependency Property called DataSource, when I set DataSource calling a custom method to Bind my dataSource to Create columns on the fly and set ItemSource property. All works fine the first time. I have a context menu called Refresh, while the user clicks Refresh the SQL will execute and the same cycle of the above-mentioned actions will happen. During the second time, the rows and columns are created perfectly. But when I do Horizontal scroll the Column headers are NOT showing properly, it loses their visual state while scrolling.
My Custom Property - DataSource
public static DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(GridDataModel), typeof(MyGridView),
new PropertyMetadata((dependencyObject, eventArgs) =>
{
if (eventArgs.OldValue != null)
{
((GridDataModel)eventArgs.OldValue).Dispose();
}
BindToDataSource((MyGridView)dependencyObject, (GridDataModel)eventArgs.NewValue);
}));
My Custom method which is calling everytime I set DataSource property:
private static void BindToDataSource(MyGridView view, GridDataModel dataModel)
{
if (view.ViewModel != null)
{
BindingOperations.ClearAllBindings(view.GridView);
view.GridView.Items.Clear();
view.GridView.Columns.Clear();
view.GridView.ItemsSource = null;
view.ViewModel.Dispose();
}
view.ViewModel = new MyGridViewModel(dataModel);
view.ViewModel.PrepareGridView();
view.LayoutRoot.DataContext = view.ViewModel;
view.CreateColumns();
view.GridView.SetBinding(DataGrid.ItemsSourceProperty, new Binding("DisplayRows"));
}
The Below code I used to call on Refresh Menu Click:
private void OnRefreshClick(object sender, RoutedEventArgs e)
{
var data = new TestDataAccess();
DataSource = data.MakeGridModel("select Top 200 * from ApplicationUSer"); //Assigning DataSource Again, which will call the above method.
GridView.UpdateLayout();
}
After refresh, you could see the column alignment goes strange when doing the horizontal scroll.
Tried using GridColumnWidth =0, and setting again to Auto, Tried GridView.UpdateLayout().
I solved the above problem my self.
Instead of BindingOperations.ClearAllBindings() i used BindingOperations.ClearBinding(view.GridView, DataGrid.ItemSourceProperty) - which cleared out only ItemSource so that i can regain memory by Items.Clear() for every time i bind the data.
Due to ClearAllBindings, its clears headers panel bindings also, so its looses ParentTemplate.Width property, because of that strange problem happend during horizontal scroll.
Is there a way in WPF to define the background of alternating visible rows?
I've tried setting the AlternationCount property, but that restarts for every child node which gives a weird look.
Ideally what I would like is to know what the visual index of a given node is. Only expanded nodes are counted.
There was no easy way to do this as WPF creates nested containers for the tree nodes. So as Rachel mentioned, looping through the items seemed to be the way to go. But I didn't want to depart too much from the built in ItemsControl.AlternationIndex attached property as that is the one people would expect. Because it is readonly I had to access it via reflection, but after that things fell into place.
First off, make sure you handle the Loaded, Expanded and Collapsed events of your TreeViewItem. In the event handler find the owning TreeView and do a recursive alternation count set of all visible nodes. I created an extension method to handle it:
public static class AlternationExtensions
{
private static readonly MethodInfo SetAlternationIndexMethod;
static AlternationExtensions()
{
SetAlternationIndexMethod = typeof(ItemsControl).GetMethod(
"SetAlternationIndex", BindingFlags.Static | BindingFlags.NonPublic);
}
public static int SetAlternationIndexRecursively(this ItemsControl control, int firstAlternationIndex)
{
var alternationCount = control.AlternationCount;
if (alternationCount == 0)
{
return 0;
}
foreach (var item in control.Items)
{
var container = control.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (container != null)
{
var nextAlternation = firstAlternationIndex++ % alternationCount;
SetAlternationIndexMethod.Invoke(null, new object[] { container, nextAlternation });
if (container.IsExpanded)
{
firstAlternationIndex = SetAlternationIndexRecursively(container, firstAlternationIndex);
}
}
}
return firstAlternationIndex;
}
}
As you can see it runs through each node and sets the custom alternation index. It checks if the node is expanded and if so continues the count on the child nodes.
Above I mentioned that you have to handle the Loaded event for the TreeViewItem. If you only handle the expanded and collapsed events you won't get the new containers that are created when a node is first opened. So you have to do a new pass when the child node has been created and added to the visual tree.
Something I've done with javascript is create an OnLoaded event for a table which loops through the table rows and if the row is visible, it sets the background color to a nextColor variable, and changes the nextColor variable to the opposite color. That might work here.