Complte event for Editor behaving like Unfocussed event - xamarin

I am trying for saving some value in the editor after clicking on done on the keyboard for that I used the Completed event for the editor but this is calling while tapping on everywhere in the view like the Unfocused event. How to avoid that this?
<controls:CustomEditor Keyboard="Default"
ReturnKeyType="Next"
TextChanged="Comment_Changed"
Completed="OnDoneClicked"
VerticalOptions="StartAndExpand"
HorizontalOptions="FillAndExpand"
Text="{Binding QuestionComment}">
<controls:CustomEditor.FontSize>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Phone>
<OnPlatform x:TypeArguments="x:Double"
iOS="12"
Android="12"
WinPhone="30" />
</OnIdiom.Phone>
<OnIdiom.Tablet>
<OnPlatform x:TypeArguments="x:Double"
iOS="13"
Android="13"
WinPhone="40" />
</OnIdiom.Tablet>
</OnIdiom>
</controls:CustomEditor.FontSize>
</controls:CustomEditor>
CustomEditorClass is as following:
public const string ReturnKeyPropertyName = "ReturnKeyType";
public CustomEditor() { }
public static readonly BindableProperty ReturnKeyTypeProperty = BindableProperty.Create(
propertyName: ReturnKeyPropertyName,
returnType: typeof(ReturnKeyTypes),
declaringType: typeof(CustomEditor),
defaultValue: ReturnKeyTypes.Done );
public ReturnKeyTypes ReturnKeyType
{
get { return (ReturnKeyTypes)GetValue(ReturnKeyTypeProperty); }
set { SetValue(ReturnKeyTypeProperty, value); }
}
public enum ReturnKeyTypes : int
{
Default,
Go,
Google,
Join,
Next,
Route,
Search,
Send,
Yahoo,
Done,
EmergencyCall,
Continue
}
And the event is as following:
private async void OnDoneClicked(object sender, EventArgs e)
{
//some logic
}

Please see the documentation, this is the intended behavior
iOS (Unfocusing the editor or pressing "Done" triggers the event). Android / Windows Phone (Unfocusing the Editor triggers the event)
I've checked some messenger apps (iOS) and they don't have a done button on the keyboard at all, they provide a send button besides the entry control.
Depending on what you try to achieve, I'd ditch the done button on the keyboard altogether and rely on some sort of view on the UI, that is used for that purpose. This is platform dependent behavior anyway, Android does not support it at all, AFAIK. In order to get rid of the done button, you'll have to implement a custom renderer and set the InputAccessoryView of the native control to null (see here)
[assembly: ExportRenderer(typeof(CustomEditor), typeof(CustomEditorRenderer))]
namespace ProjectName.iOS
{
public class CustomEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.Control.InputAccessoryView = null;
}
}
}
If you really want a button to send, you could create a derivative of Editor with a custom renderer, that creates a InputAccessoryView and raises an SendPressed event on your custom Editor when the InputAccessoryView is pressed. But remember, this is not possible for Android.

Related

Xamarin Forms Android Autosize Label TextCompat pre android 8 doesn't autosize text

I want to utilise the auto-sizing feature of android textviews in my xamarin forms solution so that as the text length grows, the font sizes shrinks to never overflow the bounds of the label, and doesn't get truncated. I've created a custom Label control to do so and added an android custom renderer. It's not working in Android 7 and below. It is working in Android 8 and above.
According to the docs autosize support was introduced in android 8, but can be supported back to Android 4 with AppCompat.v4. However, my custom rendered label just renders the default font size in Android pre 8. It works fine in 8+ devices, the label text resizes as needed to not overflow the bounds. The accepted answer to this question with a similar issue on native android says it can be to do with not setting a width and height, I've tried setting widthrequest and heightrequest explicitly and it doesn't change anything. Also setting maxlines=1 doesn't change anything. An alternative thread suggests that custom fonts are the culprit. I created a vanilla forms solution using the default device font, and get the same effect.
My code:
internal class AutosizeLabelRenderer : LabelRenderer
{
#region constructor
public AutosizeLabelRenderer(Context context) : base(context)
{
}
#endregion
#region overridable
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, autoLabel.AutoSizeMinTextSize,
autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
}
#endregion
}
public class AutoSizeLabel : Label
{
public int AutoSizeMaxTextSize
{
get => (int)GetValue(AutoSizeMaxTextSizeProperty);
set => SetValue(AutoSizeMaxTextSizeProperty, value);
}
public static readonly BindableProperty AutoSizeMaxTextSizeProperty = BindableProperty.Create(
nameof(AutoSizeMaxTextSize), // the name of the bindable property
typeof(int), // the bindable property type
typeof(AutoSizeLabel)); // the default value for the property
public int AutoSizeMinTextSize
{
get => (int)GetValue(AutoSizeMinTextSizeProperty);
set => SetValue(AutoSizeMinTextSizeProperty, value);
}
public static readonly BindableProperty AutoSizeMinTextSizeProperty = BindableProperty.Create(
nameof(AutoSizeMinTextSize), // the name of the bindable property
typeof(int), // the bindable property type
typeof(AutoSizeLabel)); // the default value for the property
public int AutoSizeStepGranularity
{
get => (int)GetValue(AutoSizeStepGranularityProperty);
set => SetValue(AutoSizeStepGranularityProperty, value);
}
public static readonly BindableProperty AutoSizeStepGranularityProperty = BindableProperty.Create(
nameof(AutoSizeStepGranularity), // the name of the bindable property
typeof(int), // the bindable property type
typeof(AutoSizeLabel)); // the default value for the property
//
}
Not working: Android 7 - text does not shrink
Working as expected: Android 8 and above
Xaml for above images:
<StackLayout HeightRequest="200" WidthRequest="100">
<Label Text="Fixed width and height, sentences get longer, text should shrink" />
<controls:AutoSizeLabel
AutoSizeMaxTextSize="50"
AutoSizeMinTextSize="8"
AutoSizeStepGranularity="1"
BackgroundColor="{StaticResource Shamrock}"
HeightRequest="40"
HorizontalOptions="Start"
MaxLines="1"
Text="A small sentence"
WidthRequest="200" />
<controls:AutoSizeLabel
AutoSizeMaxTextSize="50"
AutoSizeMinTextSize="8"
AutoSizeStepGranularity="1"
BackgroundColor="{StaticResource Shamrock}"
HeightRequest="40"
HorizontalOptions="Start"
MaxLines="1"
Text="A larger sentence that shrinks"
WidthRequest="200" />
<controls:AutoSizeLabel
AutoSizeMaxTextSize="50"
AutoSizeMinTextSize="8"
AutoSizeStepGranularity="1"
BackgroundColor="{StaticResource Shamrock}"
HeightRequest="40"
HorizontalOptions="Start"
MaxLines="1"
Text="An even larger sentence that shrinks more."
WidthRequest="200" />
</StackLayout>
TextView font size changes with the size of the control, which is new in Android 8.0 (API26),therefore, compatibility issues need to be considered when using the previous version.You could change the TextView to AppCompatTextView.
Change your
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
AppCompatTextView appCompatTextView = new AppCompatTextView(_context);
appCompatTextView.Text = Element.Text;
appCompatTextView.SetMaxLines(1);
SetNativeControl(appCompatTextView);
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control,autoLabel.AutoSizeMinTextSize,autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
}
Leo Zhu's answer got me most of the way there. There were a couple of extra steps I needed to take to get it fully working, so I'm posting the code as a separate answer here.
Differences between mine and Leo's answer:
Creating a new native control in scope like Leo suggested meant that it worked for a while but got disposed by the garbage collector and caused an exception when returning to the page after navigating away. To fix this I needed to override a property called ManageNativeControlLifetime to return false, and then manually manage disposing the object by overriding the dispose method and calling Control.RemoveFromParent();. This advice comes from a xamarin staff member in this thread.
Formatting and binding context are not automatically inherited when creating the new native control and need to be set manually. I needed to add those based on my needs using the android specific binding syntax. You may need to add other formatting and binding code based on your needs, I'm just doing font colour, gravity and binding context here.
I set the binding context with
appCompatTextView.SetBindingContext(autoLabel.BindingContext);
Once the binding context was set, I needed to add a new string property to my XF AutoSizeLabel class to pass in through XAML, then use it to set the binding path for the relevant property (In my case the text property). If more than one binding is required, you would need to add multiple new binding path properties for each required property. I set a specific binding like this:
appCompatTextView.SetBinding("Text", new Binding(autoLabel.TextBindingPath));
To facilitate this in my Xamarin Forms Xaml, my Xaml went from <Label Text="{Binding MyViewModelPropertyName}" /> to <controls:AutoSizeLabel TextBindingPath="MyViewModelPropertyName" />
Here's the full code of the renderer:
protected override bool ManageNativeControlLifetime => false;
protected override void Dispose(bool disposing)
{
Control.RemoveFromParent();
base.Dispose(disposing);
}
private AppCompatTextView appCompatTextView;
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
//v8 and above supported natively, no need for the extra stuff below.
if (DeviceInfo.Version.Major >= 8)
{
Control?.SetAutoSizeTextTypeUniformWithConfiguration(
autoLabel.AutoSizeMinTextSize,
autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity,
(int)ComplexUnitType.Sp);
return;
}
appCompatTextView = new AppCompatTextView(Context);
appCompatTextView.SetTextColor(Element.TextColor.ToAndroid());
appCompatTextView.SetMaxLines(1);
appCompatTextView.Gravity = GravityFlags.Center;
appCompatTextView.SetBindingContext(autoLabel.BindingContext);
appCompatTextView.SetBinding("Text", new Binding(autoLabel.TextBindingPath));
SetNativeControl(appCompatTextView);
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, autoLabel.AutoSizeMinTextSize, autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
}

The Command enables the Button in Xamarin Prism

I am working with Xamarin Prism, I'm trying to disable the button, but whenever I add the command it enables. What can this be ?
Code:
<Button
Command="{Binding save}"
IsEnabled="False"/>
From the document of Button.Command Property we can see:
This property is used to associate a command with an instance of a
button. This property is most often set in the MVVM pattern to bind
callbacks back into the ViewModel. IsEnabled is controlled by the
Command if set.
So, if you don't set the command property, it will work correctly.
If you set the command property, the isEnable is controlled by the Command itself.
Here I wrote an example to show you how to control the button's enable property in the command:
In the xaml:
<ContentPage.BindingContext>
<local:CommandDemoViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Button Text="Divide by 2"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Command="{Binding DivideBy2Command}"
/>
</StackLayout>
And the ViewModel:
class CommandDemoViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Command DivideBy2Command { private set; get; }
public CommandDemoViewModel()
{
DivideBy2Command = new Command(()=> performCommandAction(), ()=>isButtonEnable());
}
private bool isButtonEnable()
{
//Handle the if the button isEnable here
//Reture ture or false with your own logic here to control the button's isEnabled
return false;
}
private void performCommandAction()
{
//Handle the button click logic here
}
}
Use the constructor public Command(Action execute, Func<bool> canExecute);.
You can control the isEnable by the command's CanExecute. Reture ture or false with your own logic in the method isButtonEnable to control the button's isEnabled.

Telerik UWP DataForm changing bound data and Updating

I'm currently using telerik in UWP to create list of items, i want to be able to use a browse button and update a certain piece of data in the Telerik-RadDataForm. I have all the bindings setup using MVVM and it displays data fine if it isn't edited on the code side. My XAML is setup as so:
<Data:RadDataForm x:Name="dataform"
HorizontalAlignment="Left" Grid.Row="0"
Grid.RowSpan="2" Grid.Column="2"
VerticalAlignment="Center" Width="454"
Item="{Binding CurrentSceneViewModel, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" CommitMode="Immediate"
ValidationMode="Immediate" Height="664" Margin="0,28" />
The CurrentSceneViewModel is:
public SceneViewModel CurrentSceneViewModel
{
get => _currentSceneViewModel;
set=> _currentSceneViewModel= value;
}
And the data i wish to change is :
public string FileName
{
get => _fileName;
set
{
Scene.SceneFile = value;
_fileName = Path.GetFileName(value);
OnPropertyChanged(nameof(FileName));
}
}
The problem i have is pushing this information to the user interface the code-behind doesn't seem to update the UI, even using PropertyChanged. I'm not sure what else to try ? And if this is something the RadDataform simply doesn't support. It should be noted FileName is a property of CurrentScene ViewModel.
public abstract class BaseViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected IPageNavigationService navservice = new PageNavigationService();
}

Xamarin Forms Clickable Images

I originally implemented this feature but simply adding an image to a button. Then I realized I could simply add a tap gesture to an image (w/o using a button). Any recommendations which is the best way to go and why? Thanks.
I use my own "OnClick" event for Image :) with a custom control:
public class MyImage : Xamarin.Forms.Image
{
public static BindableProperty OnClickProperty =
BindableProperty.Create("OnClick", typeof(Command), typeof(MyImage));
public Command OnClick
{
get { return (Command)GetValue(OnClickProperty); }
set { SetValue(OnClickProperty, value); }
}
public MyImage()
{
GestureRecognizers.Add(new TapGestureRecognizer() {Command = new Command(DisTap)});
}
private void DisTap(object sender)
{
if (OnClick != null)
{
OnClick.Execute(sender);
}
}
}
Then use it with MVVM like:
<local:MyImage Source="{Binding Img}" OnClick="{Binding ImgTapCommand}" />
It depends of visual effect you want to achieve.
If you use Button you'll have tapped animation (depens of platform) and specific buttton border. You have much less control how the image will look like (it's on the left side of button text).
If you use a plain TapGestureRecognizer you'll have a normal image with full control of aspect ratio/size etc.
You could use absolute layout, which can be used to place two elements above each other, make sure to make the button is the second element.
<AbsoluteLayout>
<Image Source="clock.png" AbsoluteLayout.LayoutBounds="0.2,0.2,35,35" AbsoluteLayout.LayoutFlags="PositionProportional"/>
<Button AbsoluteLayout.LayoutBounds="0.2,0.2,35,35" AbsoluteLayout.LayoutFlags="PositionProportional" BorderColor="Transparent" BackgroundColor="Transparent" Command="{Binding AlertMeCommand}"/>
</AbsoluteLayout>

using TabControl in windows phone

I read the article about using a TabControl on Windows Phone application. I can avoid it to fire when it is first load. However, the selectionChanged fired twice when user click the tab. Would someone can help me how to fix it. Thanks in advance.
There is my TabControl:
<cc:TabControl Grid.Row="1" SelectionChanged="tabList_SelectionChanged" x:Name="tabList">
<cc:TabItem Height="80" Header="Events" Foreground="Black"/>
<cc:TabItem Height="80" Header="Details" Foreground="Black"/>
<cc:TabItem Height="80" Header="Notes" Foreground="Black" />
</cc:TabControl>
There is cobe behind:
public partial class Tab : PhoneApplicationPage
{
private bool blnFristLoad=true;
public Tab()
{
InitializeComponent();
tabList.SelectionChanged += new SelectionChangedEventHandler(tabList_SelectionChanged);
}
private void tabList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (blnFristLoad == false)
{
TabItem t = (sender as TabControl).SelectedItem as TabItem;
t.Content = "202020";
}
else blnFristLoad = false;
}
It's very obvious in your code. You are adding SelectionChanged event handler twice. One from your XAML code and the other from the code behind. As you are using += symbol, the eventhandler is added as a seperate instance.
Remove one of those statements.
Please use the Pivot control instead of a TabControl for the WindowsPhone. the Pivot control follows the design guidelines for the phone and looks and feels much better.

Resources