C#, Xamarin Forms: No Custom TextChangedEvent Raised on initialization - button

I'm creating an Xamarin.Forms MVVM App (only using Android) which needs certain buttons to be outlined red, whenever their text property holds a specific value. (Purpose: alert the user to press the button and select a value, which will change the Button Text Property and therefore remove the red outline)
To achieve this I've create the following documents:
A custom button CButton that extents the default Button:
public class CButton : Button
{
// this Hides the Default .Text-Property
public string Text
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
// The Raised Event
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
A custom behavior makes use of the raised TextChangedEvent
public class ButtonValBehavior : Behavior<CButton>
{
protected override void OnAttachedTo(CButton bindable)
{
bindable.TextChanged += HandleTextChanged;
base.OnAttachedTo(bindable);
}
void HandleTextChanged(object sender, EventArgs e)
{
string forbidden = "hh:mm|dd.mm.yyyy";
if (forbidden.Contains((sender as CButton).Text.ToLower()))
{
//Do when Button Text = "hh:mm" || "dd.mm.yyyy"
(sender as CButton).BorderColor = Color.Gray;
}
else
{
//Do whenever Button.Text is any other value
(sender as CButton).BorderColor = Color.FromHex("#d10f32");
}
}
protected override void OnDetachingFrom(CButton bindable)
{
bindable.TextChanged -= HandleTextChanged;
base.OnDetachingFrom(bindable);
}
}
The relevant parts of the ViewModel look the following:
public class VM_DIVI : VM_Base
{
public VM_DIVI(O_BasisProtokoll base)
{
Base = base;
}
private O_BasisProtokoll _base = null;
public O_BasisProtokoll Base
{
get => _base;
set
{
_base = value;
OnPropertyChanged();
}
}
Command _datePopCommand;
public Command DatePopCommand
{
get
{
return _datePopCommand ?? (_datePopCommand = new Command(param => ExecuteDatePopCommand(param)));
}
}
void ExecuteDatePopCommand(object param)
{
//launch popup
var p = new PP_DatePicker(param);
PopupNavigation.Instance.PushAsync(p);
}
}
The .xmal looks the following (b is the xmlns of the Namespace):
<b:CButton x:Name="BTN_ED_Datum"
Text="{Binding Base.ED_datum, Mode=TwoWay}"
Grid.Column="1"
Command="{Binding DatePopCommand}"
CommandParameter="{x:Reference BTN_ED_Datum}">
<b:CButton.Behaviors>
<b:ButtonValBehavior/>
</b:CButton.Behaviors>
</b:CButton>
This solution works fine whenever the input is caused by user interaction. However, when a Value is assigned during the initialization of the Page no red outline is created, in fact the TextChangedEvent isn't raised. By using breakpoints I noticed that during initialization the Text Property of CButton is never set, eventhough it actually will be in the view.
Despite fiddling around with my solution I cannot make this work on initialization. I tried to work around this issue by outlining every button by default in their constructor, however this will outline every button red, even when their text value doesn't require them to be.
How can I achieve my initial goal?
Many thanks in advance!

It's been a while but if I recall correctly what I ended up doing was:
Changing the new Text-Property of my custom Button to CText and
Making sure that I have Mode=TwoWay activated for any Element, that doesn't have it enabled by default. (Look up Binding modes on msdn for more)
making CText a bindable property of CButton
My custom button now looks the following:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace EORG_Anton.Model
{
public class CButton : Button
{
public static readonly BindableProperty CTextProperty =
BindableProperty.Create(nameof(CText),
typeof(string),
typeof(CButton),
default(string),
BindingMode.TwoWay,
propertyChanged: OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CButton)bindable;
var value = (string)newValue;
control.CText = value;
}
public string CText
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
}

Related

custom LabelRenderer shows wrong text style inside list view on scroll on Xamarin iOS

I am working on an App that requires one list view having text labels with NSUnderlineStyle on user deletion.
As per the requirement user have Delete/restore option in detail screen. On delete confirmation the text label should be underline style for that particular cell.
I am using LabelRenderer for NSUnderlineStyle in Xamarin iOS.
But currently ListView displays text Labels with underline style which is not deleted by user on list view scroll. The underline style are swapping from one cell label to another on list view scroll.
Below my sample code.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Control == null)
{
return;
}
if (this.Element is ExtendedLabel extended)
{
var strikethroughStyle = extended.IsStrikeThrough ? NSUnderlineStyle.Single : NSUnderlineStyle.None;
this.Control.AttributedText = new NSMutableAttributedString(
extended.Text ?? string.Empty,
this.Control.Font,
strikethroughStyle: strikethroughStyle);
}
}
This is the common issue of TableView Cell Resue , tableView will reuse the cell for the visible(inside screen shot) ones , so it would show the previous style .
To solve this we can forcedly set the style each time when the cell is going to display .
Create custom renderer for ListView , and do this in WillDisplay method ,before it we need to override TableView's Delegate.
[assembly: ExportRenderer(typeof(ListView), typeof(MyRenderer))]
namespace FormsApp.iOS
{
public class MyDelegate : UITableViewDelegate
{
List<Model> data;
public MyDelegate(List<Model> _data)
{
data = _data;
}
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
{
var views = cell.ContentView.Subviews;
foreach (var view in views)
{
if(view is LabelRenderer renderer)
{
UILabel label = renderer.Control;
var strikethroughStyle = data[indexPath.Row].YourProperty?NSUnderlineStyle.Single : NSUnderlineStyle.None;
label.AttributedText = new NSMutableAttributedString(
label.Text ?? string.Empty,
label.Font,
strikethroughStyle: strikethroughStyle);
}
}
}
}
public class MyRenderer : ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
IEnumerable<Model> data = (IEnumerable<Model>)Element.ItemsSource;
Control.Delegate = new MyDelegate(data.ToList());
}
}
}
}

DisplayAlert from ViewModel not displaying

I need to display DisplayAlert from the View Model, however its simply doesn't display. Is there some other way how to display alert from the VM? The permission is true so that works.
private async Task TakePicture()
{
await Permission();
var imageSource = Application.Current.MainPage.DisplayActionSheet(AppResources.AlertNewPhoto, AppResources.AlertNewPhoto, AppResources.AlertGallery);
if (imageSource.Result == AppResources.AlertNewPhoto)
}
You can change your constructor of ViewModel like following code.
public PersonsViewModel(ContentPage page){
page.DisplayAlert("info","test","Ok");
}
In your Layout background code, you can use it following code.
public partial class MainPage : ContentPage
{
PersonsViewModel personsViewModel;
public MainPage()
{
InitializeComponent();
personsViewModel = new PersonsViewModel(this);
this.BindingContext = personsViewModel;
}
If you can use plugin, you can use ACR.UserDialogs. https://github.com/aritchie/userdialogs
I solved this problem using events
public MainPageVewModel()
{
Application.Current.MainPage.Loaded += LoadCards;
}
private async void LoadCards(object sender, EventArgs e)
{
// your code on View Loaded
await Application.Current.MainPage.DisplayAlert("working alert", "alert", "ok");
}

How to use PKPaymentButton in Xamarin.Forms

I have a page in Xamarin.Forms in which I have to show PKPaymentButton by using the same PKPaymentButton class which is a child of UIButton in PassKit.
I have written a custom ButtonRenderer and trying to convert the button into PKPayment button.
I got so far that in custom renderer we can change the appearance of a button but can we use something like creating a new button instance in my case PKPaymentButton and replace it with Button.
UPDATE-
I have achieved this by-
public class ApplePayButtonRenderer : Xamarin.Forms.Platform.iOS.ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
var button = new PKPaymentButton(PKPaymentButtonType.Buy, PKPaymentButtonStyle.Black);
SetNativeControl(button);
}
}
}
Now I am trying to get its click into Xamarin.Forms
You could use Messaging Center to send message when you click the payment Button.
in Custom Renderer
var button = new PKPaymentButton(PKPaymentButtonType.Buy, PKPaymentButtonStyle.Black);
button.TouchUpInside += Button_TouchUpInside;
SetNativeControl(button);
private void Button_TouchUpInside(object sender, EventArgs e)
{
MessagingCenter.Send<Object>(this,"PayButtonClick");
}
in Forms
Add the following code to the constructor of the ContentPage which contains the Payment Button
public xxxPage()
{
InitializeComponent();
MessagingCenter.Subscribe<Object>(this, "PayButtonClick",(args)=> {
//do some thing you want
});
}

Xamarin forms check if keyboard is open or not

Is there any way to check if keyboard is open or not in Xamarin Forms? Are there any events getting fired when the keyboard opens or closes? If so, where can I find an example of it?
I don't believe that there's a Xamarin.Forms way of doing it. Anyway, for the different platforms (at least Android and iOS) there is a way to achieve what you want.
Android
Under android there is InputMethodManager class. You can obtain it from your activity
var inputMethodManager = (InputMethodManager)this.GetSystemService(Context.InputMethodService);
Now you can check if the keyboard is shown with
var keyboardIsShown = inputMethodManager.IsAcceptingText;
According to this article on CodeProject you can use a class derived from IOnGlobalLayoutListener to listen to global layout events. When this event has fired, you can use the code above to check, if the layout has been changed due to the keyboard popping up.
iOS
Under iOS you may use UIKeyboard class which allows you to observe the DidShowNotification (see here).
notification = UIKeyboard.Notifications.ObserveDidShow ((sender, args) => {
Debug.WriteLine("Keyboard is shown.");
// whatever
});
similarly you can observe DidHideNotification (and some others - see here).
Xamarin.Forms
To implement the keyboard-notification in your Xamarin.Forms the easiest way will be to implement platform dependencies which are resolved with the DependencyService. To do this, you'll first have to introduce an interface for the platform service.
public interface IKeyboardService
{
event EventHandler KeyboardIsShown;
event EventHandler KeyboardIsHidden;
}
In your platform specific projects you'll have to implement the functionality in a platform specific way. See the following code section for iOS implementation
[assembly: Xamarin.Forms.Dependency(typeof(Your.iOS.Namespace.KeyboardService))]
namespace Your.iOS.Namespace
{
public class KeyboardService : IKeyboardService
{
public event EventHandler KeyboardIsShown;
public event EventHandler KeyboardIsHidden;
public KeyboardService()
{
SubscribeEvents();
}
private void SubscribeEvents()
{
UIKeyboard.Notifications.ObserveDidShow(OnKeyboardDidShow);
UIKeyboard.Notifications.ObserveDidHode(OnKeyboardDidHide);
}
private void OnKeyboardDidShow(object sender, EventArgs e)
{
KeyboardIsShown?.Invoke(this, EventArgs.Empty);
}
private void OnKeyboardDidHide(object sender, EventArgs e)
{
KeyboardIsHidden?.Invoke(this, EventArgs.Empty);
}
}
}
The Xamarin.Forms.Dependency makes the class visible to the DependencyService. See the following code for Android implementation
[assembly: Xamarin.Forms.Dependency(typeof(Your.Android.Namespace.KeyboardService))]
namespace Your.Android.Namespace
{
public class KeyboardService : IKeyboardService
{
public event EventHandler KeyboardIsShown;
public event EventHandler KeyboardIsHidden;
private InputMethodManager inputMethodManager;
private bool wasShown = false;
public KeyboardService()
{
GetInputMethodManager();
SubscribeEvents();
}
public void OnGlobalLayout(object sender, EventArgs args)
{
GetInputMethodManager();
if(!wasShown && IsCurrentlyShown())
{
KeyboardIsShown?.Invoke(this, EventArgs.Empty);
wasShown = true;
}
else if(wasShown && !IsCurrentlyShown())
{
KeyboardIsHidden?.Invoke(this, EventArgs.Empty);
wasShown = false;
}
}
private bool IsCurrentlyShown()
{
return inputMethodManager.IsAcceptingText;
}
private void GetInputMethodManager()
{
if (inputMethodManager == null || inputMethodManager.Handle == IntPtr.Zero)
{
inputMethodManager = (InputMethodManager)this.GetSystemService(Context.InputMethodService);
}
}
private void SubscribeEvents()
{
((Activity)Xamarin.Forms.Forms.Context).Window.DecorView.ViewTreeObserver.GlobalLayout += this.OnGlobalLayout;
}
}
}
In your Xamarin.Forms app you can now obtain an instance of the correct implementation of IKeyboardService with
var keyboardService = Xamarin.Forms.DependencyService.Get<IKeyboardService>();
In Xamarin Forms in ANDROID CODE change
(InputMethodManager)this.GetSystemService(Context.InputMethodService);
with
(InputMethodManager)Xamarin.Forms.Forms.Context.GetSystemService(Context.InputMethodService);
You need to change:
var inputMethodManager = (InputMethodManager)this.GetSystemService(Context.InputMethodService);
To:
InputMethodManager inputMethodManager = (InputMethodManager)((Activity)Android.App.Application.Context).GetSystemService(Context.InputMethodService);

AspectJ capture button clicked

I want to know whether how to capture the button clicked with AspectJ and get its parameter (eg. button name). I think for having more generalized capturing with AspectJ, it shoudl be used MouseListener so it can capture other UI elements in general!
Example:
In a GUI example I have defined 2 buttons that take some actions
public JButton btn1 = new JButton("Test1");
public JButton btn2 = new JButton("Test2");
btn1.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
//take some actions
}
}
btn2.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
//take some actions
}
}
How to capture these buttons with AspectJ, and get their parameters (eg. name)?
It is possible. I have provided two examples. The first that prints out for every JButton that has an ActionListener. The other example only prints out if a specific buttons is clicked.
Prints the text for every JButton clicked with an ActionListener:
#Pointcut("execution(* *.actionPerformed(*)) && args(actionEvent)")
public void buttonPointcut(ActionEvent actionEvent) {}
#Before("buttonPointcut(actionEvent)")
public void beforeButtonPointcut(ActionEvent actionEvent) {
if (actionEvent.getSource() instanceof JButton) {
JButton clickedButton = (JButton) actionEvent.getSource();
System.out.println("Button name: " + clickedButton.getText());
}
}
Prints the text for a specific JButton:
public static JButton j1;
#Pointcut("execution(* *.actionPerformed(*)) && args(actionEvent) && if()")
public static boolean button1Pointcut(ActionEvent actionEvent) {
return (actionEvent.getSource() == j1);
}
#Before("button1Pointcut(actionEvent)")
public void beforeButton1Pointcut(ActionEvent actionEvent) {
// logic before the actionPerformed() method is executed for the j1 button..
}
UPDATED:
You can do this in many different ways. For example add your buttons to the aspect directly. But I prefere to use a enum object between (ButtonManager in this case), so the code does not know about the aspect. And since the ButtonManager is an enum object, it is easy for the aspect to retrieve values from it.
I just tested it with a Swing button class from Oracle and it works. In the Swing class:
b1 = new JButton("Disable middle button", leftButtonIcon);
ButtonManager.addJButton(b1);
AspectJ is extremely powerful when it comes to manipulating classes, but it can not weave advises into specific objects since objects is not created at the time of weaving. So you can only work with objects at runtime and that is why I have added the addJButton(..) method above. That enables the aspect to check the advised button against a list of registered buttons.
The ButtonManager class:
public enum ButtonManager {
;
private static Collection<JButton> buttonList = new LinkedList<JButton>();
public static void addJButton(JButton jButton) {
buttonList.add(jButton);
}
public static Collection<JButton> getButtonList() {
return buttonList;
}
}
Modified pointcut and advice to only print the name of the buttons registered in the ButtonManager:
#Pointcut("execution(* *.actionPerformed(*)) && args(actionEvent) && if()")
public static boolean buttonListPointcut(ActionEvent actionEvent) {
Collection<JButton> buttonList = ButtonManager.getButtonList();
JButton registeredButton = null;
for (JButton jButton : buttonList) {
if (actionEvent.getSource() == jButton) {
registeredButton = jButton;
}
}
return registeredButton != null;
}
#Before("buttonListPointcut(actionEvent)")
public void beforeButtonListPointcut(ActionEvent actionEvent) {
JButton clickedButton = (JButton) actionEvent.getSource();
System.out.println("Registered button name: " + clickedButton.getText());
}
UPDATED 2
Okay, I believe I understand what you want. You want to listen to mouse events. That is possible. The downside is that you have to register all your GUI components that you want to listen for clicks with a mouse listener. It is not enough to register the JPanel of the JFrame with a MouseListener. So if you only have registered an ActionListener for your buttons, you also have to add a mouse listener.
I have created a quick solution that works for me. It only shows that it works. I have not tried to make the solution generic with many different GUI objects. But that should be quite easy to refactor in when you have got the basics to work.
In the Swing class:
private class MouseListener extends MouseInputAdapter {
public void mouseClicked(MouseEvent e) {}
}
In the init method of the Swing class:
MouseListener myListener = new MouseListener();
btn1.addMouseListener(myListener);
btn2.addMouseListener(myListener);
In the Aspect class:
#Pointcut("execution(* *.mouseClicked(*)) && args(mouseEvent)")
public void mouseEventPointcut(MouseEvent mouseEvent) {}
#Before("mouseEventPointcut(mouseEvent)")
public void beforeMouseEventPointcut(MouseEvent mouseEvent) {
if (mouseEvent.getSource() instanceof JButton) {
JButton clickedButton = (JButton) mouseEvent.getSource();
System.out.println("aspectJ --> mouseClicked: " + clickedButton.getText());
}
}
This results in the following output in the console:
aspectJ --> mouseClicked: Test1
I hope it helps!

Resources