I have a WPF .NET 4.6 application running on a Windows 8.1 tablet and for the last few days I've been struggling to make my app touch friendly to make it work as expected. My main problems are focus related, these affect several controls in my app. for example:
Textboxes: sometimes requires a double or triple touch in order to get input focus, they do enter a mouse over state but the caret isn't there;
ComboBoxes: takes a couple of touches in order to open it, and once touching an item in order to select it the combo stays open with the newly selected item highlighted;
Buttons: takes a couple of clicks to run the connected command and stay in mouse over state;
Keyboard support
There are a couple of approaches I tried while searching for a solution that each has it's own downsides:
Removing the tablet support for the entire application (taken from here). this one solves most of the focus problems mentioned above but makes scrolling (and I guess some other Tablet related functionality that I haven't found yet) unusable.
Explicitly activating the keyboard when required (Example here). Focus problem remains, scrolling works as expected
I also tried to remove all styles and tested everything on 2 different tablets from different manufacturers but without success
Recently Microsoft announced that "Touch is better" But I couldn't find any official documentation about the best way to approach this subject.
Any suggestion on how to make my application work better with touch would be a big help.
I was able to remove mouse over state by using following behavior:
public class TouchDeviceMouseOverUIElementFixBehavior : Behavior<UIElement>
{
protected override void OnAttached()
{
AssociatedObject.StylusUp += AssociatedObject_StylusUp;
}
protected override void OnDetaching()
{
AssociatedObject.StylusUp -= AssociatedObject_StylusUp;
}
private void AssociatedObject_StylusUp(object sender, StylusEventArgs e)
{
var control = sender as FrameworkElement;
if (control != null)
{
if (!VisualStateManager.GoToElementState(control, "Normal", true))
{
VisualStateManager.GoToState(control, "Normal", true);
}
}
}
}
Related
Having experiences various forms of flickering and graphical glitches, I searched online for possible solution. The only thing that worked straight away was accepted solution using NativeWinAPI from post just below:
Avoid Flickering in Windows Forms?
Inserting this code in the main form of the application and keeping handle for 'this' practically eliminated every issue I had with graphics.
At least until I included a web browser (WebView2 Control). This control along side with the code from the post causes the control itself to constantly repaint itself. This in turn causes graphical issues within entire User Control that is parent to the WebView2. Other controls flicker in and out, which is super annoying and unpleasant.
Having spent hours(days really) trying to figure out what is wrong and practically rewriting entire project, the issue was located and it disappears straight after disabling function that sets the window style.
I am fairly certain that WebView2 Control is the only control having issues as I created OnPaint functions that write to console every time that the control was repainted, and disabling webview2 stops other controls from being repainted, while when enabled I get 100's of repaints within few seconds.
The problem is that disabling those changes makes the application look even worse with all the flickering and graphical glitches that it was fixing before.
I do not understand what the code from the link exactly does (too advanced/complex for my current knowledge). If anyone could help me figure out how to solve the issue I would really appreciate it.
Update:
I created a small demo project for anyone interested in addressing this.
It is a 7zip of the project placed on google drive:
FlickeringDemo.7z
Microsoft Edge Canary Browser is required for WebView2 to work correctly:
Download Edge Canary Here
Main form has bool flag that control graphical improvements and flickering. Simply set it to true/false to observe the difference.
Debug.WriteLine(); - will output Paint Event counter into console in Visual Studio.
bool FlickerEnabled = false;
public MainForm()
{
InitializeComponent();
if (FlickerEnabled)
{
InitialiseGraphicalFixes();
}
}
I was having the same problem in my project and I've managed to solve it.
In the provided FickeringDemo.zip sample, to stop flickering with that version of WebView2 (1.0.664.37) you should set to true the DoubleBuffered property in all of your .Net Controls; see code below for an example on how to do it.
Another thing that I've found is that this fix stops to working in WebView2 1.0.774.44; in that version the rest of your controls will not flick, but the WebView2 will flick a lot. I didn't find a way to solve it...
public partial class WebViewControl : UserControl
{
...
public WebViewControl()
{
...
//the fix to solve flicker if the WS_EX_COMPOSiTED is set on the top window
SetDoubleBuffered(this, true);
}
private void SetDoubleBuffered(Control control, bool value)
{
try
{
control.GetType().GetProperty("DoubleBuffered", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(control, value, null);
}
catch
{
}
foreach (Control child in control.Controls)
{
SetDoubleBuffered(child, value);
}
}
I have a scrollView with a Grid inside which contains several Entrys.
When I click anywhere on the screen that isn't one of the Entry controls the focus automatically goes to the first Entry I have on the grid. I.e. This happens whether any Entry already has Focus or not, it will always set the focus of the first one again.
If I remove the Scrollview and have the Grid on screen on it's own I don't get this issue.
I am developing an application for a Windows 10 device but using the Xamarin forms cross platform code as we may move the code to Android at some stage.
Thanks in advance.
I was able to reproduce the described behavior on UWP Windows 10, but not on Android or iOS, so this is a bug in the Forms code for UWP.
I have filed a bug report for this issue which you can track here: https://bugzilla.xamarin.com/show_bug.cgi?id=52613
Xamarin engineers will now discuss this issue on the bug report. If you would like to receive a notification when the bug is updated, you can add yourself to the CC list for the bug. Please note that you will need to create an account on that system if you have not already done so.
I don't know if you solved your problem because it was more than a year ago and the issue #jgoldberger has opened on Xamarin's Bugzilla did not progress since last October.
Nevertheless I just noticed the same behaviour on my UWP app and after looking around the internet I found a solution.
If you look at this thread : Why does my TextBox get focused when clicking inside of ScrollViewer?
Then it is easy to imagine a solution for a Xamarin.Forms app by creating a custom renderer inside your UWP project.
You can use the following code which is working perfect for me :)
using {your_project_namespace}.UWP;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP;
[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewCustomRenderer))]
namespace {your_project_namespace}.UWP
{
public class ScrollViewCustomRenderer : ScrollViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ScrollView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
Control.IsTabStop = true;
}
}
}
Hope this helps.
Cheers
I've noticed that when OnElementPropertyChanged is fired on a VisualElement like a BoxView, the properties of the underlying platform view are not updated at that time.
I want to know when the VisualElement's corresponding platform view is finished rendering, something like:
this.someBoxView.ViewHasRendered += (sender, e) => {
// Here I would know underlying UIView (in iOS) has finished rendering
};
Looking through some code inside of Xamarin.Forms, namely VisualElementRenderer.cs, it would seem that I could raise an event after OnPropertyChanged has finished. Something like:
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
SetBackgroundColor(Element.BackgroundColor);
else if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName)
UpdateClipToBounds();
else if (e.PropertyName == PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty.PropertyName)
SetBlur((BlurEffectStyle)Element.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty));
// Raise event
VisualElement visualElement = sender as VisualElement;
visualElement.ViewHasRendered();
}
Naturally there's a few more complexities to adding an event to the VisualElement class, as it would need to be subclassed. But I think you can see what I'm after.
While poking around I've noticed properties on VisualElement like IsInNativeLayout. But that only seems to be implementing in Win/WP8. Also, UpdateNativeWidget on VisualElementRenderer as well, however I can't figure out the proper way to leverage them.
Any ideas?
Much appreciated.
TL;DR : Run away, do not go down this path...
On iOS everything that displays content to the screen happens within a UIView (or subclass) and drawRect: is the method that does the drawing. So when drawRect: is done, the UIView is drawing is done.
Note: Animations could be occurring and you might see hundreds of completed rendering cycles completed. You might need to hook into every animation's completion handler to determine when things really are done "rendering".
Note: The drawing is done off-screen and depending upon the iDevice, the screen refresh Hz could 30FPS, 60FPS or in the case of iPad Pro it is variable (30-60hz)...
Example:
public class CustomRenderer : ButtonRenderer
{
public override void Draw(CGRect rect)
{
base.Draw(rect);
Console.WriteLine("A UIView is finished w/ drawRect: via msg/selector)");
}
}
On Android the common way to draw content is via a View or subclass, you could obtain a surface, draw/bilt via OpenGL to a screen, etc... and that might not be within a View, but for your use-case, think Views..
Views have Draw methods you can override, and you can also hook into ViewTreeObserver and monitor OnPreDraw and OnDraw, etc, etc, etc... Sometimes you have to monitor the View's parent (ViewGroup) to determine when drawing is going to be done or when is completed.
Also all standard Widgets are completely inflated via xml resources and that is optimized so you will never see a Draw/OnDraw method call (Note: You should always(?) get a OnPreDraw listener call if you force it).
Different Views / Widgets behave differently and there no way to review all the challenges you will have determining when a View is really done "rendering"...
Example:
public class CustomButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer,
ViewTreeObserver.IOnDrawListener, ViewTreeObserver.IOnPreDrawListener
{
public bool OnPreDraw() // IOnPreDrawListener
{
System.Console.WriteLine("A View is *about* to be Drawn");
return true;
}
public void OnDraw() // IOnDrawListener
{
System.Console.WriteLine("A View is really *about* to be Drawn");
}
public override void Draw(Android.Graphics.Canvas canvas)
{
base.Draw(canvas);
System.Console.WriteLine("A View was Drawn");
}
protected override void Dispose(bool disposing)
{
Control?.ViewTreeObserver.RemoveOnDrawListener(this);
Control?.ViewTreeObserver.RemoveOnPreDrawListener(this);
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control?.SetWillNotDraw(false); // force the OnPreDraw to be called :-(
Control?.ViewTreeObserver.AddOnDrawListener(this); // API16+
Control?.ViewTreeObserver.AddOnPreDrawListener(this); // API16+
System.Console.WriteLine($"{Control?.ViewTreeObserver.IsAlive}");
}
}
}
Misc:
Note: Layout Optimizations, Content caching, GPU caching, is hardware acceleration enabled in the Widget/View or not, etc... can prevent the Draw methods from being called...
Note: Animation, effects, etc... can cause these these methods to be call many, many, many times before an area of the screen is completely finished displaying and ready for user interaction.
Personal Note: I've gone down this path once due to a crazy client requirements, and after banging my head on the desk for some time, a review of the actual goal of that area of the UI was done and I re-wrote the requirement and never tried this again ;-)
I'm going to answer my own question it hopes that the solution will help someone who is struggling with this issue in the future.
Follow #SushiHangover's advice and RUN don't WALK away from doing something like this. (Although his recommendation will work and is sound). Attempting to listen/be notified when the platform has finished rendering a view, is a terrible idea. As #SushiHangover mentions there's simply too many things that can go wrong.
So what brought me down this path?
I have a requirement for a pin code UI similar to the one in iOS to unlock your device, and in many other apps. When a user presses a digit on the pad I want to update the corresponding display "box" (boxes above the pad). When the user inputs the last digit I want the last "box" to be filled in, in my case a Background color change, and then execution to continue in which the view would transition to the next screen in the workflow.
A problem arose as I tried to set the BackgroundColor property on the fourth box and then transition the screen. However, since execution doesn't wait for the property to change the screen transitions before the change is rendered. Naturally this makes for a bad user experience.
In an attempt to fix it, I thought "Oh! I simply need to be notified when the view has been rendered". Not smart, as I've mentioned.
After looking at some objective C implementations of similar UIs I realizes that the fix it quite simple. The UI should wait for a brief moment and allow the BackgroundColor property to render.
Solution
private async Task HandleButtonTouched(object parameter)
{
if (this.EnteredEmployeeCode.Count > 4)
return;
string digit = parameter as string;
this.EnteredEmployeeCode.Add(digit);
this.UpdateEmployeeCodeDigits();
if (this.EnteredEmployeeCode.Count == 4) {
// let the view render on the platform
await Task.Delay (1);
this.SignIn ();
}
}
A small millisecond delay is enough to let the view finished rendering without having to go down a giant rabbit hole and attempt to listen for it.
Thanks again to #SushiHangover for his detailed response. You are awesome my friend! :D
I am currently developing an application in windows phone 8.1 And I am having problems with the soft system navigation bar.
I can hide the bar, but I can't seem to find if it is visible or not.
To hide it, I could use:
ApplicationView.GetForCurrentView().SuppressSystemOverlays = true;
The problem is that some phones have it, some don't. I need to find if it is active so that I can bind the height of my VirtualizingStackPanel correctly.
Thank you,
As far as i know there is no perfect way to work with the none-hardware nav bar, and i think that your best option here is to subscribe to the ApplicationView.VisibleBoundsChanged event,
so basically what you need to do is :
set the ApplicationViewBoundsMode to UseCoreWindow so that the content will be laid out in the region occupied by the core phone window :
ApplicationView.GetForCurrentView().SetDesiredBoundsMode(ApplicationViewBoundsMode.UseCoreWindow);
subscribe to the VisibleBoundsChanged event and handle you UI changes inside the handler:
ApplicationView.GetForCurrentView().VisibleBoundsChanged += handler;
//...
private void handler(ApplicationView sender, object args)
{
//handle ui changes
}
I have a full screen WPF application built for a touch monitor, and I have some Listboxs on the main screen.
When I flick the 'Listbox' it scrolls fine, but when it gets to the end of the list, the entire application gets pulled down from the top of the screen, can I stop this behavior somehow?
Has anyone else seen this?
Yes, that default behaviour of the ListBox (or rather, the ScrollViewer inside the default ListBox template) is weird - when I first came across it, I thought it must be a practical joke. In fact, it's really hard to find any documentation about it - but it is briefly mentioned here:
The ManipulationBoundaryFeedback event enables applications or components to provide visual feedback when an object hits a boundary. For example, the Window class handles the ManipulationBoundaryFeedback event to cause the window to slightly move when its edge is encountered.
So, a way around it is to handle ManipulationBoundaryFeedback on the ListBox, and set Handled to true:
<ListBox ManipulationBoundaryFeedback="OnManipulationBoundaryFeedback">
// ...
</ListBox>
Code-behind:
private void OnManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
{
e.Handled = true;
}