I'm currently in the process of developing a SideDrawer for Xamarin.Forms, because at this point, the one from telerik is rather awful sideeffect-wise.
I know how to do this in WPF, since it's rather easy, but in Xamarin it's way different.
My code for the GestureFrame is pretty much the same as this.
I've used the sources at some github project/xamarin docs/XLabs to get started. At first it was going well, but as soon as i'm placing controls within the gestureframe i will not receive any events anymore, because the childcontrols appear to consume any touch/gesture events there are.
Does this ring a bell to anyone? Right now i'm not sure what i might be doing wrong for the control to behave this way
The Only Gestures that Xamarin Forms handles currently are Tap and DoubleTap these bubble up by default. For Android, Windows and presumably IOS each handle other gestures differently.
Quick Review of Event Handling in the Xamarin.Forms world:
On Android
Gestures are handled by the Renderer each renderer has a Touch event. Touch is raised in the renderer when a gesture occurs. By subscribing to the Touch event and intupreting the EventArgs you can determine what is happening on the screen. Now you could make all the determinations yourself of what the user is doing or use the Mono.Android.GestureDetector to make those decisions for you. GestureDetector requires a GestureListener which it notifies when it believes an event like a tap or double have occured. Your Gesture listener can then contain whatever code you want to respond to these events.
On Windows
Each native control determines for itself When an event has occurred and exposes a set of EventHandlers for those events. To respond to these events you create a custom renderer and subscribe to the events on the native controls that then execute your own code.
On IOS?
Don't know yet haven't got that far in my project https://github.com/Indiponics/IndiXam-Lib maybe someone else can give you that piece.
Bubbling up the Events
Lets look at a simple bubbling situation:
public class App : Application
{
public App()
{
// The root page of your application
MainPage = new ContentPage
{
Content = new Frame
{
Content =
new Label {
Text = "Hold Me, Thrill Me, Kiss Me"
}
}
};
}
}
Lets put some Custom Renderers together and look at whats happening. To start with we'll need a renderer for every control in the stack so in our case a Label Renderer and a Frame Renderer.
We'll Start with Windows:
[assembly: ExportRenderer(typeof(Label), typeof(myLabelCustomRenderer))]
[assembly: ExportRenderer(typeof(Frame), typeof(myFrameCustomRenderer))]
namespace App4.WinPhone
{
public class myFrameCustomRenderer:FrameRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Frame> e)
{
base.OnElementChanged(e);
if(e.NewElement!=null)
{
this.Control.Hold += Control_Hold;
}
}
void Control_Hold(object sender, System.Windows.Input.GestureEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Frame Held");
e.Handled = false;
}
}
public class myLabelCustomRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Label> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
this.Control.Hold += Control_Hold;
}
}
void Control_Hold(object sender, System.Windows.Input.GestureEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Label Held");
e.Handled = false;
}
}
}
Running this we find that
Bubbling actually occurs by default in windows. If we wanted we could turn off bubbling by changing
e.Handled = true;
In our Label Renderer and the frame would never get notified of the Hold Event.
Now For Android
On Android things get a bit messier. Again we'll create two renderers.
[assembly: ExportRenderer(typeof(Label), typeof(myLabelCustomRenderer))]
[assembly: ExportRenderer(typeof(Frame), typeof(myFrameCustomRenderer))]
namespace App4.Droid
{
public class myFrameCustomRenderer : FrameRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
this.Touch += myFrameCustomRenderer_Touch;
}
}
void myFrameCustomRenderer_Touch(object sender, Android.Views.View.TouchEventArgs e)
{
System.Diagnostics.Debug.WriteLine("You Touched My Frame");
e.Handled = false;
}
}
public class myLabelCustomRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Label> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
this.Touch += myFrameCustomRenderer_Touch;
}
}
void myFrameCustomRenderer_Touch(object sender, Android.Views.View.TouchEventArgs e)
{
System.Diagnostics.Debug.WriteLine("You Touched My Label");
e.Handled = false;
}
}
}
If we run this it appears that everything works the same as windows we geta touch event in the label and a touch event in the Frame. The bubbling up appears to be automatic. It Gets messy when we attempt to disable bubbling. If we change
e.Handled=true;
in the Label Renderer and run the app again---
Touch fires twice IN THE LABEL RENDERER. Once for when we touch the screen and once for when we stop. If we set the labelrenderer's e.Handled=false; and set the Frame to true. Then the label touch fires followed by the Frame but only the Frame Fires the second time.
In addition if we remove e.Handled=false from both renderer and run the app we find that only the LabelRenderer's Touch event fires. Implying that the default for Handled appears to be true. If you do not set e.Handled=false in the renderer the event will fire in the LabelRenderer and not bubble up the stack to the FrameRenderer.
In Conclusion:
Bubbling works out of the box on Windows. On Android it doesn't work like you might expect. First you have to explicitly set the Handled=false in every child so the parent gets notification and even then only the Handler that Handled the event gets notified that the touch event ended the rest of the stack gets notified of the start but never knows its over.
Related
I want to open a form, make a screenshot and close it.
So I tried the Shown Event, but this fired before everything is painted.
Since Shown is the last event fired (according to MSDN), what can I do?
I think just delaying isn't such a good idea in an event driven environment.
Any advice? Thx in adv.
<
{
InitializeComponent();
UpdateData();
this.Shown += new System.EventHandler(this.PrintForm_Shown);
this.ShowDialog();
}
private void PrintForm_Shown(object sender, EventArgs e)
{
DoPrintForm();
Close();
}
>
So I have the following code:
<TabBar Route="Dashboard">
<Tab Title="Dashboard" AutomationId="DashboardId">
//more codes here
</Tab>
<Tab AutomationId="AddNewId">
//more codes here
</Tab>
<Tab Title="Statistics" AutomationId="StatisticsId">
//more codes here
</Tab>
</TabBar>
Note that in my MainActivity's OnCreate() I have set up the following:
Xamarin.Forms.Forms.ViewInitialized += (object sender, Xamarin.Forms.ViewInitializedEventArgs e) => {
if (!string.IsNullOrWhiteSpace(e.View.AutomationId))
{
e.NativeView.ContentDescription = e.View.AutomationId;
}
};
This works perfectly with my other elements except for the TabBar items. Somehow the TabBar items are getting the Title property and setting is at the accessibilityId/content-dec.
Anyone knows why this is and how can I make it so it will get the right AutomationId? Thanks
There are multiple issues with AutomationId on Android.
The underlying problem is discussed in Android - Using AutomationId prevents TalkBack screenreader accessibility:
Xamarin.Forms "borrows" the ContentDescription property on Android for Automation IDs. These IDs polute Android's TalkBack accessibility tree, making apps almost impossible to navigate.
This means you can support test automation or accessibility, not both. Our app needs to support both.
In the case of Tabs, presumably Xamarin code is doing what you see: copying Title to content-desc, so that Android text readers will speak it.
The suggested work-around is to write custom renderer(s) that do what you need. Described in a comment by codingL3gend :
i was able to find a work around to this issue by creating a customrenderer and respective custom component to allow for overriding the native android method(s) that get triggered when accessibility events are fired. you will need to create some bindable properties on your custom component that you can access in the custom renderer to allow for setting the content description value to what you want but that's simple enough.
this method gets triggered in the control/custom renderer whenever an accessibility event is fired
public override bool OnRequestSendAccessibilityEvent(Android.Views.View child, AccessibilityEvent e)
{
if (AccessibilityHandler.IsAccessibilityEnabled(_context) && child != null)
{
if (!string.IsNullOrEmpty(_automationId) && _automationId.Equals(child.ContentDescription))
{
child.ContentDescription = $"{_automationName} {_helpText}";
}
}
return base.OnRequestSendAccessibilityEvent(child, e);
}
then you can set the contentDescription value of the control/custom renderer back to what the automationId value was originally when the control/custom renderer is detached from the view.
protected override void OnDetachedFromWindow()
{
base.OnDetachedFromWindow();
if (!string.IsNullOrEmpty(_automationId))
{
Control.ContentDescription = _automationId;
}
}
helper class
public static class AccessibilityHandler
{
public static bool IsAccessibilityEnabled(Context context)
{
var accessibility = (AccessibilityManager)context.GetSystemService(MainActivity.AccessibilityService);
return accessibility?.GetEnabledAccessibilityServiceList(Android.AccessibilityServices.FeedbackFlags.Spoken)?.Count > 0;
}
}
If you only need AutomationId during testing, or you can live with the effect this has on Accessibility Screen Readers (esp. it won't be multi-lingual), then you could make a much simpler custom renderer for use when testing.
Put this in your custom renderer (if isn't Tab, then change <Tab> to appropriate Xamarin class):
protected override void OnElementChanged( ElementChangedEventArgs<Tab> e )
{
base.OnElementChanged( e );
if (e.OldElement != null)
{
// Removing previous element.
// TBD: Remove obsolete references. (usually not needed)
}
if (Element == null)
// Going away with no replacement.
return;
UpdateAutomationId();
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Element.AutomationId))
{
UpdateAutomationId();
}
}
void UpdateAutomationId()
{
var _automationId = Element.AutomationId;
if (!string.IsNullOrEmpty(_automationId))
{
Control.ContentDescription = _automationId;
}
}
In xamarin.forms I want to have a button that will only works if is pressed by 5 seconds
How can I detect that?
Unfortunately there is no OnTouchDown/OnTouchUp-like events available in Xamarin.Forms.
The Clicked event and the TapGestureRecognizer occur after the touch is complete (ie. press is released).
But you can create a custom control subclassing Button, with the following properties:
public event EventHandler PressStarted;
public event EventHandler PressEnded;
And the following public methods:
public void OnPressStarted()
{
if (PressStarted != null)
{
PressStarted(this, EventArgs.Empty);
}
}
public void OnPressEnded()
{
if (PressEnded != null)
{
PressEnded(this, EventArgs.Empty);
}
}
Then create a renderer per platform to be able to count the time that has pressed the button.
You can use your own button with using Mr.Gesture plugin (paid plugin).
It has OnTouchDown/OnTouchUp event.
In normal version of silverlight you can create an event handler by registering it by EventManager. Windows Phone 7 hasn't got that class.
My question is: How to create an event, which will be handled by the parent panels.
My scenario: I've created a custom class with some textbox in it. Foreach I've added my custom behavior, which raises when textblock is clicked. Behavior works like: "When this Textblock in custom control is clicked, please raise a custom event with my custom args (i want to pass them to the Custom Control itself (for example to specify to which VisualState change it)."
Can you help me how to handle my problem?
Could you provide sample code of what you are trying to do? it seems you want to create an event for when the TextBlock is clicked.
Add an event handler to the textblock:
public Event EventHandler<RoutedEventsArgs> TextClicked;
// Fire the event
private void OnTextClicked(object sender, RoutedEventArgs e)
{
if (TextClicked != null)
{
TextClicked(sender, e);
}
}
TextBlock.Click =+ OnTextBlockClicked;
private void OnTextBlockClicked(object sender, RoutedEventArgs e)
{
// Raise event
OnTextClicked(sender, e);
}
Something along those lines I think.
I taken two Images & added event (MouseButtonDown) for them.
When first image handles event to open Gallery. Second image handles events for open camera.
When user has choosed his image from the gallery, I want to navigate to next page. Its navigates. But before completing navigation process, it displays MainPage & then moves toward next page. I didnt want to display the MainPage once user chooses the image from the gallery.
Plz help.
Thanks in advance.
public partial class MainPage : PhoneApplicationPage
{
PhotoChooserTask objPhotoChooser;
CameraCaptureTask cameraCaptureTask;
// Constructor
public MainPage()
{
InitializeComponent();
objPhotoChooser = new PhotoChooserTask();
objPhotoChooser.Completed += new EventHandler<PhotoResult>(objPhotoChooser_Completed);
cameraCaptureTask = new CameraCaptureTask();
cameraCaptureTask.Completed += new EventHandler<PhotoResult>(objCameraCapture_Completed);
}
void objPhotoChooser_Completed(object sender, PhotoResult e)
{
if (e != null && e.TaskResult == TaskResult.OK)
{
//Take JPEG stream and decode into a WriteableBitmap object
App.CapturedImage = PictureDecoder.DecodeJpeg(e.ChosenPhoto);
//Delay navigation until the first navigated event
NavigationService.Navigated += new NavigatedEventHandler(navigateCompleted);
}
}
void navigateCompleted(object sender, EventArgs e)
{
//Do the delayed navigation from the main page
this.NavigationService.Navigate(new Uri("/ImageViewer.xaml", UriKind.RelativeOrAbsolute));
NavigationService.Navigated -= new NavigatedEventHandler(navigateCompleted);
}
void objCameraCapture_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
//Take JPEG stream and decode into a WriteableBitmap object
App.CapturedImage = PictureDecoder.DecodeJpeg(e.ChosenPhoto);
//Delay navigation until the first navigated event
NavigationService.Navigated += new NavigatedEventHandler(navigateCompleted);
}
}
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
}
private void image1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
objPhotoChooser.Show();
}
private void image2_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
cameraCaptureTask.Show();
}
To my knowledge when you use one of the choosers, like the Photo gallery or the camera, when your application is activating it will take it back to the page you left it. I don't think there is a way to get around this itself. What you would have to do is catch the Activating event in your main page code and Navigate to the desired page from there.
Now I am not completely sure how you would pass the image from the MainPage to the target page. It does not look like there is a property in the Navigation service to store this value. But you could either set it in an application wide variable, ModelView or even store it in the Isolated Storage area.
You could work around this by navigating to an intermediate blank page and have that intermediate page launch the tasks. When the tasks are completed you can then navigate as normal to your new page and only this blank page will show in transit.
Chris is correct that some of the tasks will navigate away from your app (effectively tombstoning it) and will the re-activate your application when the user returns from the task. For the camera this is particularly difficult, as to my knowledge there is no simple way to detect when you are returning from the camera. Also the camera doesn't work when attached to the debugger or Zune software (at least this is true on my HTC Surround), which makes troubleshooting quite difficult!
In my WP7 Barcode Scanning application I ended up using flags on the PhoneApplicationService class to help track where the navigation events are coming from. Something like:
PhoneApplicationService.Current.State["ReturnFromSampleChooser"] = true;
You can then check for these flags in the PhoneApplicationPage_Loaded or OnNavigatedTo method of your main page and redirect to the desired page as needed. Just make sure to clear the flag and be careful to not cause any loops in the navigation, as that might make your app fail certification (back button must ALWAYS work correctly).
For an example of how to use the camera and set/clear flags using PhoneApplicationService check out the source code for the Silverlight ZXing Barcode Library. You can download the full source here or browse the files online.