wpf Change cell selection inside CurrentCellChanged event handler does not work - wpf

I have a wpf datagrid with SelectionUnit set to "CellOrRowHeader" and SelectionMode set to "Extended".
<DataGrid SelectionUnit="Cell" SelectionMode="Extended" Name="myDataGrid">
CurrentCellChanged="onCurrentCellChanged"
</DataGrid>
Inside the onCurrentCellChanged I tried to change the selection to another cell:
private void onCurrentCellChanged(object sender, EventArgs e)
{
DataGridCell cell = DataGridHelper.GetCell(myDataGrid, 3, 3);
cell.IsSelected = true;
cell.Focus();
}
The GetCell is a helper function. I verified that the cell is not null, and the codes are executed. However, I only see the cell {3,3} is focused (with a black border), but not highlighted (without blue background).
The strange thing is that if I call the same code, but not inside onCurrentCellChanged event callback, the cell {3,3} got highlighted.
Anybody knows the reason ? Many thanks!

Besides "CurrentCellChanged", There is another event "SelectedCellsChanged" that acts differently. After some tests, I figured out that these two events serve different purpose. For example, in some cases, the "CurrentCellChanged" gets called even if the selection is not changed. While that's beyond the scope of this answer...
Back to the topic, I managed to resolve the problem by manipulating the cell selection inside SelectedCellsChanged event call back. BUT there is some trick:
Change the markup:
<DataGrid SelectionUnit="Cell" SelectionMode="Extended" Name="myDataGrid">
SelectedCellsChanged="onSelectedCellsChanged"
</DataGrid>
Here is the handler:
private void onSelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
// trick: Since the code below change the cell selection, which may
// cause this handler to be called recursively, I need to do some filter
// If myDataGrid.CurrentCell != e.AddedCells.First(), return
// Here we can change cell selection
DataGridCell cell = DataGridHelper.GetCell(myDataGrid, 3, 3);
cell.IsSelected = true;
}

Related

WPF ComboBox SelectionChanged event firing twice

In my DataGrid I am using DataGridComboBoxColumn as follows. Its SelectionChanged event (defined below) always fires twice - once when I click on an item, and then again when I select the new item from the dropdown. When I click on the item that I want to change the SelectionChanged event fires and shows the old value, and then when I select on a new value it fires again and correctly show the new value. But I want the event to be fired only when I select a new value for the combobox.
Question: What is causing this behavior and how can the issue be fixed?
Remark: Many users online seem to have similar issues posted here but none of them helped resolve my issue - maybe, the context is a bit different here. Moreover, the XAML and the code seem ok as it correctly displays the combobox values along with the correctly combox selected values for each row in the grid. Plus, the SelectionChanged event does correctly show the newly selected value but when it fires the second time. Similar code is shown here.
<DataGridComboBoxColumn Header="StartTime" SelectedItemBinding="{Binding localTime}" ItemsSource="{StaticResource localTimeList}">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<EventSetter Event="SelectionChanged" Handler="MyComboBoxColumn_SelectionChanged"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
The event:
private void MyComboBoxColumn_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
var selectedVal = comboBox.SelectedValue.ToString();
}
What is causing this behavior?
The SelectionChanged event is raised initially when you enter the edit mode and the SelectedItem property is being bound to your source property.
How can the issue be fixed?
The easiest way to handle this is to check whether the ComboBox has been loaded and simply return from the event handler immediately if it hasn't:
private void MyComboBoxColumn_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
if (!comboBox.IsLoaded)
return;
//handle an actual selection here...
}

Using ScrollIntoView on DataGrid with Checkbox changes behavior

I seem to have conflicting requirements. I have a DataGrid that has a checkbox as the first column. The users want the checkbox to be selectable with a single click, not a double click. I was able to make that happen by using a DataGridTemplateColumn and a checkbox like this:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The user also a control that allows them to specify a row (there can be hundreds of rows). If they specify a row that isn't in view I want it to scroll into view. I compromised and added an event handler in the code behind for the DataGrid_SelectionChanged event. Originally I was just using the ScrollIntoView command but offscreen rows would get highlighted but the grid did not scroll them into view. I was then able to add a Focus command and the row scrolled into view. So now the event handler looks like this:
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid dg = (DataGrid)sender;
if (dg.SelectedItem == null) return;
dg.ScrollIntoView(dg.SelectedItem);
dg.SelectedItem.Focus();
}
Now I'm back to the original problem, the row scrolls into view but to check the checkbox on any other row (that you don't move into via the jump to row control) you have to click twice. Anybody know what is causing the rows moved to manually to require double clicks?
Well I got it to work with the following code which was inspired by some tangentially related posts on getting the focus into cells. I have no clue as to why ScrollIntoView doesn't work, working or why performing the last three lines was the one way I could get the row to scroll into view without disabling the checkbox.
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid dg = (DataGrid)sender;
if (dg.SelectedItem == null) return;
dg.ScrollIntoView(dg.SelectedItem);
DataGridRow dg_row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromItem(dg.SelectedItem);
if (dg_row == null) return;
dg_row.Focus();
}

Combobox selectionchanged event triggers without even changing the selection in the ComboBox

Could you help me find the error in this one: The event triggers before even the windows form is loaded. I start to see the message Box and then I click OK,after that it loads the main screen.After that everything works perfectly, I wonder what triggers the ComboBox SelectionChanged Event before even loading the window.The FillComboBoxFamilyData(SegmentCode) just creates a dataset and puts the values int he ComboBox. Please Refer to this link for complete code.
Not able to make cascading comboboxes work
Any help would be highly appreciated.Thanks.
<ComboBox Height="23" HorizontalAlignment="Left" Margin="35,26,0,0" Name="comboBox1" VerticalAlignment="Top" Width="205" ItemsSource="{Binding Source={StaticResource tblSegmentViewSource}}" DisplayMemberPath="Segment Name" SelectedValuePath="Segment Code" SelectionChanged="comboBox1_SelectionChanged"/>
<ComboBox Margin="304,26,395,93" Name="comboBox2" />
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show(comboBox1.SelectedValue.ToString());
SegmentCode = Convert.ToInt32(comboBox1.SelectedValue.ToString());
FillComboBoxFamilyData(SegmentCode);
}
At the moment the data will be loaded (attached by the binding), SelectionChanged will be fired. Therefore, you have to check in your event-handler if your app is ready and all the data is loaded and attached. If not, return the event-handler without doing anything. This behaviour is by design.
ItemsSource="{Binding Source={StaticResource tblSegmentViewSource}}"
You can use the IsLoaded-property to detect, if the binding already has been evaluated. IsLoaded will not be true unless the databinding-engine has evaluated your xaml-bindings.
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e) {
if(!IsLoaded){
return;
}
.... your code here
You can use IsLoaded property of the combo box to test whether it is loaded yet. This is the cleanest and easiest solution which I could find:
var comboBox = (ComboBox)sender;
if (!comboBox.IsLoaded)
{
// This is when the combo box is not loaded yet and the event is called.
return;
}
I know this is an old question but I came across it twice trying to fix this in my project and had the same results as the OP. My list is populated after the IsLoaded is true. So, I figured I would post what I figured out for others. Just use the DropDowOpened event to set a bool to true. This way the SelectionChanged event won't fire until the user actually clicks on the dropdown.
private bool UserSeriesChange;
private void comboBox1_DropDownOpened(object sender, EventArgs e)
{
UserSeriesChange = true;
}
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(!UserSeriesChange){
return;
}
.... your code here
I had this same problem and I found out that setting the starting-selection-index of the combox using xaml will trigger the selectionchanged event when program is loading which causes the error.
To solve you can either set the selection-index to -1 (the default) OR change the current-selection-index of the combobox using code after the program has loaded.

How can I put a Silverlight 3 DataGridCell into edit mode in code?

I want to be able to pick a specific cell in a Silverlight 3.0 DataGrid and put it into edit mode. I can use the VisualTreeManager to locate the cell. How do I switch to edit mode?
Each DataGridCell looks like this in the VisualTreeManager:
System.Windows.Controls.DataGridCell
System.Windows.Controls.Grid
System.Windows.Shapes.Rectangle
System.Windows.Controls.ContentPresenter
System.Windows.Controls.TextBlock
System.Windows.Shapes.Rectangle
System.Windows.Shapes.Rectangle
with the TextBlock containing the text I want to edit.
Update
Following #AnthonyWJones' suggestion, here's how I tried to do this using BeginEdit().
I wanted to keep it simple so I thought I'd pick a column in the first row. Even that proved beyond my SL knowledge! In the end, I get the first row by creating a field called firstRow to hold it:
private DataGridRow firstRow;
added a LoadingRow handler to the DataGrid:
LoadingRow="computersDataGrid_LoadingRow"
and
private void computersDataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
if (this.firstRow == null)
this.firstRow = e.Row;
}
and then adding a button to the panel to trigger the edit:
private void Button_Click(object sender, RoutedEventArgs e)
{
this.dataGrid.SelectedItem = this.firstRow;
this.dataGrid.CurrentColumn = this.dataGrid.Columns[4];
this.dataGrid.BeginEdit();
}
I click the button and the correct cell is selected but it doesn't go into edit on the cell. It takes a manual click to achieve that.
I'm not sure why you need to find the DataGridCell using VisualTreeManager nor do I know currently how you would properly start editing . You may get away with simply setting the cell's visual state to editing.
VisualStateManager.GoToState(myDataGridCell, "Editing", true);
I'm not sure how the grid behaves when you do something like the above. You may find things goe a bit pearshaped if you need DataGrid to help you revert changes to a row.
The "standard" approach would be to set the DataGrid SelectedItem property to the item represented by the row, set the CurrrentColum property to the DataGridColumn object that represents to the column in which the cell is found. Then call the BeginEdit method.
I am not able to understand your problem properly, but I had a similar problem
I wanted to make only few of the Grid Cells editable and rest were not. Instead of creating a logic and assigning ReadOnly as true/ false, I did the simple thing.
Mark the whole Grid's cells are writable, IsReadOnly as false
Set the event PreparingCellForEdit and send a callback
When you double click on a cell, it gets in the edit mode
Check whether this cell you want to be editable
If it is allowed to be edited, go ahead
If that cell is ReadOnly, then call CancelEdit
The sample code goes like
namespace foo
{
public class foobar
{
public foobar()
{
sampleGrid = new DataGrid();
sampleGrid.IsReadOnly = false;
sampleGrid.PreparingCellForEdit += new EventHandler<DataGridPreparingCellForEditEventArgs>(sampleGrid_PreparingCellForEdit);
}
void sampleGrid_PreparingCellForEdit(object sender, DataGridsampleGrid_PreparingCellForEditEventArgs e)
{
if (sampleGrid.SelectedItem != null)
{
bool isWritableField = CheckIfWritable()
if (isWritableField == false)
{
sampleGrid.CancelEdit();
}
// continue with your logic
}
}
private DataGrid sampleGrid;
}
}

Synchronizing scroll positions for 2 WPF DataGrids

I am trying to synchronize the horizontal scroll position of 2 WPF DataGrid controls.
I am subscribing to the ScrollChanged event of the first DataGrid:
<toolkit:DataGrid x:Name="SourceGrid" ScrollViewer.ScrollChanged="SourceGrid_ScrollChanged">
I have a second DataGrid:
<toolkit:DataGrid x:Name="TargetGrid">
In the event handler I was attempting to use the IScrollInfo.SetHorizontalOffset, but alas, DataGrid doesn't expose IScrollInfo:
private void SourceGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
((IScrollInfo)TargetGrid).SetHorizontalOffset(e.HorizontalOffset);
// cast to IScrollInfo fails
}
Is there another way to accomplish this? Or is there another element on TargetGrid that exposes the necessary IScrollInfo to achieve the synchronization of the scroll positions?
BTW, I am using frozen columns, so I cannot wrap both DataGrid controls with ScrollViewers.
There is great piece of code to do this:
http://www.codeproject.com/KB/WPF/ScrollSynchronization.aspx
According to the Microsoft product group, traversing the visual tree to find the ScrollViewer is the recommended method, as explained in their answer on Codeplex.
We had this same problem when using the Infragistics grid because it didn't (still doesn't) support frozen columns. So we had two grids side-by-side that were made to look as one. The grid on the left didn't scroll horizontally but the grid on the right did. Poor man's frozen columns.
Anyway, we ended up just reaching into the visual tree and pulling out the ScrollViewer ourselves. Afterall, we knew it was there - it just wasn't exposed by the object model. You could use a similar approach if the WPF grid does not expose the ScrollViewer. Or you could subclass the grid and add the functionality you require to make this work.
Interested in hearing why you need to do this.
This is a great solution. Worked fine for me in WPF.
http://www.codeproject.com/Articles/39244/Scroll-Synchronization
I just made a reference to ScrollSynchronizer dll, added a xml import:
xmlns:scroll="clr-namespace:ScrollSynchronizer"
then just added this to both my datagrids and bobs your uncle:
<DataGrid.Resources>
<Style TargetType="ScrollViewer">
<Setter Property="scroll:ScrollSynchronizer.ScrollGroup" Value="Group1" />
</Style>
</DataGrid.Resources>
You can trick the datagrid to expose its ScrollViewer as public property for each grid, when for example innerGridControl_ScrollChanged() handler called during initialisation of the usercontrol.
To expose it you can make your grid in an xaml View file, and then compose two of them in another xaml View.
Below code is on the innerGrid.xaml.cs for example:
public ScrollViewer Scroller { get; set; } // exposed ScrollViewer from the grid
private bool _isFirstTimeLoaded = true;
private void innerGridControl_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (_isFirstTimeLoaded) // just to save the code from casting and assignment after 1st time loaded
{
var scroller = (e.OriginalSource) as ScrollViewer;
Scroller = scroller;
_isFirstTimeLoaded = false;
}
}
on OuterGridView.xaml put an attached event handler definition:
<Views:innerGridView Grid.Row="1" Margin="2,0,2,2" DataContext="{Binding someCollection}"
x:Name="grid1Control"
ScrollViewer.ScrollChanged="Grid1Attached_ScrollChanged"
></Views:innerGridView>
<Views:innerGridView Grid.Row="3" Margin="2,0,2,2" DataContext="{Binding someCollection}"
x:Name="grid2Control"
ScrollViewer.ScrollChanged="Grid2Attached_ScrollChanged"
></Views:innerGridView>
then access that public ScrollViewer.SetHorizontalOffset(e.HorizontalOffset) method when another scrolling event occur.
Below code is in the OuterGridView.xaml.cs on one of the handler definition (
private void Grid1Attached_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e != null && !e.Handled)
{
if (e.HorizontalChange != 0.0)
{
grid2Control.Scroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
e.Handled = true;
}
}
private void Grid2Attached_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e != null && !e.Handled)
{
if (e.HorizontalChange != 0.0)
{
grid1Control.Scroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
e.Handled = true;
}
}
Also make sure any other scroll_changed event inside the inner grid (if any, for example if you define a TextBox with default scroller in one of the column data template) has its e.Handled set to true to prevent outer grid's handler processing it (this happened due to default bubbling behaviour of routedevents). Alternatively you can put additional if check on e.OriginalSource or e.Source to filter the scroll event you're intended to process.

Resources