How to handle events generated by Grid Splitter in WPF? - wpf

I want an event handler that handles the event when the grid splitter is being moved, Im not sure if there is one, if not, I guess I can generated an event when the size of the rows are changed?
Thanks.

You could do the rows changing size, but GridSplitter itself is a Thumb and so has its own events such as DragStarted and DragCompleted. More details here.
Edit: If you make the GridSplitter focusable and allow it to be moved with the keyboard, read the answer by Benlitz for more information.

I didn't tested, but I'm pretty sure that the currently accepted answer from AresAvatar won't work if you're resizing the rows/columns using keyboard arrows (by giving the focus to the grid splitter). This is a rare but possible case that you should anticipate in your application.
When the grid splitter is moved (either by drag'n'drop or using keyboard arrows), it changes the Width/Height dependency properties of the ColumnDefinitions/RowDefinitions of the grid. You can easily register a handler on this property change:
var heightDescriptor = DependencyPropertyDescriptor.FromProperty(RowDefinition.HeightProperty, typeof(ItemsControl));
heightDescriptor.AddValueChanged(myGrid.RowDefinitions[0], HeightChanged);
(This snippet will for instance track size change in the first row of the grid).
Then you can handle resize in an handler that will work in every case.
private void HeightChanged(object sender, EventArgs e)
{
// TODO: handle row resize
}
Generally, it is really not advised to rely on the user input action (mouse dragging, keyboard inputs...) to handle a logical or visual actions/events, since there are almost always several ways to do the same actions using different inputs (mouse, keyboards, touchscreen, ease-of-use tools...).

Use DragDelta. Be aware that if you want to change the size, use as the Actual property as the current size like ActualWidth instead of Width.

Related

How to detect styling applied to DataGridView via e.CellStyle.BackColor in the CellFormatting event?

If we've applied styling (e.CellStyle.BackColor say) to some rows via the CellFormatting event of a DataGridView, is it then possible to detect that styling at a later stage?
For example, currently we use a generic bloc of code to handle printing and exporting to Excel for any and all our DataGridViews. Until now the code hasn't catered for any styling.
So we want to add it in.
If we check the .DefaultCellStyle of the row or cell then our styling doesn't show up (it just shows as 0 or Black, which is completely wrong).
I assume that's because we've applied the styling via a CellFormatting event, instead of embedding it into the DefaultCellStyle.
Unfortunately I could not find a complete solution to your issue, only a work around.
Some experimenting with the CellFormatting event using the example from MSDN resulted in me seeing exactly what you were seeing - the BackColor was clearly being set but the CellStyle was not reflecting that. 1
The work around I found was to not use the DataGridViewCellFormattingEventArgs CellStyle property but to instead go straight to the grid. This has the downside that you now need to manually handle the case where you do not want to format the cell.
I've got some code below showing this - it is again just modifying the MSDN code:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
// If the column is the Artist column, check the
// value.
if (this.dataGridView1.Columns[e.ColumnIndex].Name == "Artist")
{
if (e.Value != null)
{
// Check for the string "pink" in the cell.
string stringValue = (string)e.Value;
stringValue = stringValue.ToLower();
if ((stringValue.IndexOf("pink") > -1))
{
// With the commented line below we cannot access the new style
//e.CellStyle.BackColor = Color.Pink;
// With this line we can!
dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.BackColor = Color.Pink;
}
else
{
// With the original MSDN code the else block to reset the
// cell style was not needed.
dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.BackColor = dataGridView1.DefaultCellStyle.BackColor;
}
}
}
}
1. My theory is that this is similar to the confusion people have over the .Refresh() method, where the DataGridView has two very distinct views of itself, one being the rectangle painted on screen and the other being the underlying data. With the .Refresh() method you only repaint the rectangle, you do not refresh the data. I think this is like that - the CellFormatting event only formats during painting and doesn't do anything to the grid styles themselves.
A possible solution would be to add a second handler to the generic printing block of code (just before the actual printing). This handler should be attached to the CellFormatting event and only save the e.cellstyle in a temporary storage (such as a dictionary of cellstyles).
All cellstyles applied during your original cellformatting will be readable in your generic printing code without the need of adjusting specific cellformatting-events that have been tied to the datagridview.
At the end of the printing you can remove the handler again.
See also Is there a way to force a DataGridView to fire its CellFormatting event for all cells?

Using more than 144 adorners

It appears that the maximum number of adorners that work without any breakage is 144.
I have a ScrollView with a bunch of objects, and many of them come with adorners. The first 144 adorners are positioned correctly, but the rest are not. Note that it is an exceptional situation when there are so many; usually there are exactly zero adorners. Nevertheless, I'd like this to work properly even on that exceptional occasion.
Leaving aside how this arbitrary (and very low) limit makes me feel, are there any practical work-arounds for this bug?
At this time there is no known way of doing this.
Which is just as well, because I found the performance to be poor; simply subclassing my Image control that was supposed to display the adorner, and drawing the overlay in the OnRender, worked much better (and unlike WinForms, the visual can extend beyond the logical boundary of the control).
Here is the scenario under which I managed to implement a workout for this problem:
I have a number of textboxes that are linked to an Excel document.
The textboxes take a numerical value. They are set to invalidate on data errors in the xaml code. A data error occurs if the number is < 1, or null.
I placed an AdornerDecorator around the textbox (so that the red invalidation border appears correctly over the textbox).
In Excel, you can alter all the textboxes at the same time - but, as the OP found, if you manage to invalidate over 144 text boxes at once, the adorner decorator starts playing up, offsetting the position of the borders (the very thing it was designed to fix in the first place).
I tried a number of different solutions including invalidating the layout, however none of these worked for the situation I was facing.
Using Snoop, I found that if I refresh the textbox manually, the adorner then placed itself correctly. So, I decided to call an update to the layout from each individual textbox that needed the adorner. I did this by listening for OnValueUpdated on the textboxes. If the new value it was updating to happened to be an invalid value, I force an "UpdateLayout()" for the textbox (I only wanted to do this for invalid values as forcing an update impacts performance, and I don't want to do that every time the value changes).
In this way, regardless of the number of cells I wanted to change at once, the adorner decorator was always displayed in the correct position (except for the very last textbox to be evaluated which, despite my best efforts, is always ever so slightly misaligned).
This might be way late to the party here, but this seemed to solve the problem for me. I kept a list of the adorners that I had added to the adorner layer (called m_adorners), and in the root control where my adorners were contained, I attach to the LayoutUpdated event. Here's event handler:
private void OnLayoutUpdated(object sender, EventArgs e)
{
if (m_adorners.Any(a => !a.IsArrangeValid &&
a.Parent != null))
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
adornerLayer.InvalidateArrange();
}
}

How To Handle Two-Finger Tap Event ? WPF

In my program I would like to make two different things, one with the single touch, second with the two finger touch. But I don’t know how to handle the difference between single touch and two finger touch.
What should I do to achive that ?
Thanks.
UIElement has the ManipulationStarted event. The ManipulationStartedEventArgs has a property named Manipulators (IEnumerable). The count of Manipulators is the number of contacts being used (e.g. 2 elements would mean 2 fingers).
Also available is the ManipulationDelta event. The args there also has a Manipulators property.

MVVM WPF datagrid data entry problem

I have a datagrid in my MVVM application which, because of the way the client wants the data displayed, needs use template columns. They want some typical data-entry functionality though (pressing enter performs data checking, commits the row if valid, focuses on the first textbox of the next row; pressing tab focuses the next textbox...). Also, data is often imported from an external source into the grid, usually thousands of records at a time.
Right now, I have a Loaded event hooked up to each textbox which I am using to set focus after new rows are added. My problem is that the grid goes haywire when I import a lot of rows. As the user scrolls, the Loaded events are fired, and the grid becomes basically unusable. I disabled virtualization to prevent this, and find my grid taking up a gig of RAM in certain configurations, which is unacceptable. I can't figure out a way to get this grid to work the way they require without using a huge amount of memory. It seems as if I just need to be able to focus a textbox within a newly added row, but since the data validation is performed in the viewmodel, I don't have access to the new row in codebehind, so I can't just call "newtextbox.focus()" or whatever. I'm getting pretty desperate here, any suggestions would be massively appreciated.
Put an event listener in the code behind that can call your newtextbox.focus() (and whatever else you want to do). In the validation in the view model, fire the event with args that indicate what you want your grid to do.
Edit: OK, new approach. Try trapping the keystrokes, and on enter or tab do the things you want it to do.
This would be in your xaml
<Grid KeyUp="myDataGrid_KeyUp" >
This would go in your code-behind
private void myDataGrid_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
// do your enter stuff here, manipulate the view model for validation, etc.
}
else if (e.Key == Key.Tab)
{
// do your tab stuff here (get selected row, determine cell focus, focus on next cell)
}
}

Databinding falls behind event notification - discussion

Found an interesting problem that I first found in WinForms, and found again in Silverlight, and more than likely WPF as well when it comes to databinding.
I have a tab control with several tabs. As users click across the tabs, each time should be valid before allowing the user to switch from the tab.
For example, user is in a text box which is updated. Binding of text boxes is not flushed until the control loses focus. Loss of focus occurs when the cursor is moved from the control, and focus is given to another control.
In this scenario, the user tabs into a control (let's use text box for this example), and updates the text box. At this point the databinding has not flushed the control, and hence the VM has not yet seen the change. The user then uses their mouse to click the next tab of the control.
At this point things get interesting. I used the PreviewSelectionChanged (Telerik RadTabControl), as I want to check things out before the jump to the next tab occurs, and it also gives me the ability to cancel the event.
However, when I look at the VM, in this event, it still does not have the updated data. I see the VM is clean, and go ahead and allow the jump to the next tab.
As soon as this event is over however, the databindings flush, and the VM gets updated. what now? The events are out of sync! When the mouse was used to click the next tab, the textbox should have lost focus, flushed it's bindings, before the Preview of the Tab click! It's to late to jump back and say oops we didn't catch that in time!
I think I found an interesting work around to this issue - but I'm not 100% sure it will work 100% of the time. I cancel the current event, but then I use the Dispatcher and create a delegate pointing to another method with the same signature as the current event. The Dispatcher will add this message to the message pump, which by this time will now (hopefully?) be behind the messages of the VM updating...
My two questions are:
1) I'm assuming that the textbox control either didn't flush when the mouse left the control, or the process that was fired was too slow and hence the preview message was on the pump before the databinding - either way I see this to be a major issue.
2) Is the workaround a good solution?
Ok, first to answer question 1:
Just because the mouse left the textbox area, doesn't mean that the textbox lost focus. It only loses focus once something else gets focus. For example, if you moved the mouse out of the textbox and click on some other control on your page (it can be anything from a scroll viewer to another textbox, etc.) then your textbox will lose focus.
Now, based on that, the events do not happen in the wrong order. What happens is; your click event on the other tab triggers both the textbox to lose focus (and the data binding to take place) and the move to the next frame, and based on that, you basically get a race condition where the moving to the next tab happens before the databinding takes place.
About question 2:
What you can do is, set the UpdateSourceTrigger to Explicit, you will however be forced to then have some kind of text_changed event and manually update the binding.
You can read more about that here. It might not be the most complete explanation but is a good place to start.
On the other hand, you can associate some events to the textbox and force the textbox to lose focus on those events (e.g. mouse out).
Just an idea: Why not do everything in the VM's PropertyChanged event?
protected override void OnThisViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
if(e.PropertyName == "WhateverProperty") {
//Do your magic here for whatever you want to set
}
}
Have your TabItems bound to a collection that will control is being disabled or not.
<sdk:TabControl>
<sdk:TabItem IsEnabled="{Binding SomeProperty, Converter={AmIDisabledOrWhatConverter}}" />
</sdk:TabControl>
That way, everything is triggered whenever a property is chaned in the vm. No more timing issues since everything is on the vm.
Just my two cents.
There's a design defect here, and you're trying to work around the defect instead of fixing it. You shouldn't have to figure out how to cancel the Click event on the tab. The tab shouldn't be processing Click events in the first place.
Generally speaking, if it's not legal for the user to click on a control, the control shouldn't be enabled. The tab should be disabled until the state of the view model is valid.
Your view model should be exposing a command for navigating to the next tab, and the tab should be bound to the command. The command's CanExecute method should only return true when the state of the view model on the current tab is valid.
This doesn't fix your other problem, which is that Silverlight doesn't support UpdateSourceTrigger="PropertyChanged" out of the box. But that's a solved problem (here is one example).
Note that if you implement commands to handle this wizard-like navigation in your application, you can, down the road, change the view to use something other than a tab control (e.g. to use navigation buttons like an actual wizard, or something like Telerik's PanelBar) without having to screw around with event handlers.
Change your bindings to include UpdateSourceTrigger="PropertyChanged".
This will ensure that your data sources are updated on every key stroke, not just LostFocus.
MyOwnTextBox()
{
this.TextChanged += (s, e) => UpdateText();
}
private void UpdateText()
{
BindingExpression be = GetBindingExpression(TextProperty);
if (be != null && be.ParentBinding.Mode == BindingModes.TwoWay)
{
be.UpdateSource();
}
}
I am using this class it updates my binding on the fly, however there is issue with empty string and null values, if your destination property is nullable string then this will update empty string in your destination property. You can get workaround by using some sort of string converter which will use null instead of empty string in case of nullable strings.

Resources