Xamarin Forms Clickable Images - image

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>

Related

.net maui CarouselView is not quite compatible with Slider control: CarouselView swipe operation takes over Slider's drag action

.net maui app.
Dragging value element along the slider bar does not work if the the slider put into CarouselView's template like this:
<CarouselView ItemsSource="{Binding Items}">
<CarouselView.ItemTemplate>
<DataTemplate>
<Slider Minimum="0" Maximum="30" WidthRequest="200" />
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
CarouselView takes over the swipe event for scrolling through the items, and Slider does not get the event (DragStarted is not even called). You can actually click along the slider bar to change its value, so it's not completely frozen, but not how it's supposed to work. Drag & drop is main way user deal with slider control.
Could anyone advise any workaround? I want users to be able scroll through carousel view items also. It's just if they swipe inside the control, event should not handed over to its parent container, if it's possible to do so.
If I add it outside of the corouselview, combine both in Grid and use padding to align slider inside the corouselview, it works as expected, but I need to add lots of additional code, calculate the desirable location and redirect all bindings, which ends up to be an awkward workaround.
At first, I don't suggest you use the slider in the CarouselView. Becasue you want the same behavior has two effects. There is a conflict between them.
But for the android, you can use the custom handler to deal with the swipe event.
Put the Handler class in the /Platform/Android:
public class MySliderHander : SliderHandler
{
protected override void ConnectHandler(SeekBar platformView)
{
base.ConnectHandler(platformView);
platformView.SetOnTouchListener(new SliderListener());
// the listener will make the slider deal with the swip event not the CarouselView.
}
}
Put the SliderListener class in the /Platform/Android
public class SliderListener : Java.Lang.Object, IOnTouchListener
{
public bool OnTouch(global::Android.Views.View v, MotionEvent e)
{
if (e.Action == MotionEventActions.Down || e.Action == MotionEventActions.Move)
{
v.Parent.RequestDisallowInterceptTouchEvent(true);
}
else
{
v.Parent.RequestDisallowInterceptTouchEvent(false);
}
return false;
}
}
And in the MauiProgram.cs:
builder
                  .UseMauiApp<App>()      
                  .ConfigureMauiHandlers(handlers => {
#if ANDROID
                        handlers.AddHandler(typeof(Slider), typeof(YourProjectName.Platforms.Android.MySliderHander));
                  #endif
                  })
In addition, the Slider's height is same as the CarouselView. So you can use a frame to contain the Slider and swipe the CarouselView by swiping the frame.

In a Xamarin MVVM application, how can I change what I see on the screen from the ViewModel?

My application viewModel responds to a user clicking a button to see test results:
private void AddDetailRows(List<QuizHistory> quizHistoryList)
{
quizDetails.Children.Clear();
quizDetails.Children.Add(AddData(quizHistoryList));
quizDetails.Children.Add(new LineTemplate());
}
Where quizDetails is the name of an element in the view.
But this doesn't work for me as the view model doesn't know what the view looks like and does not have access to the names of elements.
In a MVVM application, how is this problem solved?
You are completely right, that is not something that ViewModel is responsible of.
So, whatever you want to do with UI is not responsibility of the ViewModel.
If this is really the only option, then you can think of creating boolean properties in your VM and binding them to your views and then changing that boolean from false to true or vice versa on button click command which is binded to your VM.
To simplify it:
MyView.xaml
<StackLayout>
<Button Command="{Binding ShowHideQuizHistoryCommand}" ... />
<StackLayout x:Name="QuizHistory"
IsVisible={Binding ShowQuizHistory }>
//
</StackLayout>
</StackLayout>
MyViewModel.cs
private bool _showQuizHistory ;
public bool ShowQuizHistory
{
get { return _showQuizHistory ; }
set
{
_showQuizHistory = value;
OnPropertyChanged();
}
}
public ICommand ShowHideQuizHistoryCommand => new Command(() =>
{
ShowQuizHistory = !ShowQuizHistory;
});
So, this is just an example based on what you provided in question.
You can also use visual states, converters, triggers and behaviors in order to achieve this, but in my opinion this is the easiest way.

Frame goes transparent on tap

I am working on a Xamarin.Forms project with a ListView and each item has a Frame with a white background. My problem is I recently noticed when I tap the Item it makes the Frame's background white. It still shows all the other objects but the frame goes transparent.
The frame is simply
<Frame CornerRadius="10" Padding="0" Margin="10, 10, 10, 5" BackgroundColor="White">...</Frame>
Promoting above comments-answer to real-answer for readability sake:
The behavior you are seeing is specific to iOS. You can solve it by overriding the default behavior using an effect, like so:
[assembly: ResolutionGroupName("MyEffects")]
[assembly: ExportEffect(typeof(ListViewHighlightEffect), nameof(ListViewHighlightEffect))]
namespace MyProject.iOS.Effects
{
public class ListViewHighlightEffect : PlatformEffect
{
protected override void OnAttached()
{
var listView = (UIKit.UITableView)Control;
listView.AllowsSelection = false;
}
protected override void OnDetached()
{
}
}
}
Then, you can apply it in your view code-behind:
MyListView.Effects.Add(Effect.Resolve($"MyEffects.ListViewHighlightEffect"));
I did a short write-up on the full solution here
Alternatively, if you want to maintain the ability to select an item, one possible fix would be to add an ItemTapped handler in your code behind and null out
MyListView.SelectedItem = null;

how to use radio buttons in xamarin forms

Creating a Registration page, I need to get the following data from user.
First Name
Last Name
Username
Email
Password
Date of Birth
Gender
User Role
For the last two parameters, I am unable to find how to use radio buttons in Xamarin.Forms. Following is my code for the Registration Page.
<StackLayout BackgroundColor="#30af91" Padding="60">
<Entry Text="{Binding FirstName}" Placeholder="First Name"/>
<Entry Text="{Binding LastName}" Placeholder="Last Name"/>
<Entry Text="{Binding UserName}" Placeholder="Last Name"/>
<Entry Text="{Binding Email}" Placeholder="Email" />
<Entry Text="{Binding Password}" Placeholder="Password" IsPassword="True"/>
<Entry Text="{Binding ConfirmPassword}" Placeholder="Confirm Password" IsPassword="True"/>
<DatePicker MinimumDate="1/1/1948" MaximumDate="12/31/2007"/>
<!--Radio buttons for Gender
1. Male 2.Female-->
<!--Radio Buttons for UserRole
1. Admin 2.Participant-->
<Button Command="{Binding RegisterCommand}" Text="Register"/>
<Label Text="{Binding Message}" />
</StackLayout>
Xamarin forms does not provide Radio Button.
You can either use
1)Switch
2)Picker
or any other component to fulfill your requirement
UPDATE
The xamarin forms update version 4.6 has introduced the Radio button control, Here is the official documentation
I think there is a simpler solution that is fairly easy and requires no libraries. Really a a radio group is just a fancy ListView. You would just need to create a viewModel for each radio button that has a IsSelected flag and switch between 2 images. I had a need to allow a user to select how long a token persisted:
XAML
<ListView
HasUnevenRows="True"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding Durations}"
ItemSelected="ListView_ItemSelected"
SelectedItem="{Binding SelectedDuration}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout
Orientation="Horizontal">
<Image
HeightRequest="18"
IsVisible="{Binding IsSelected}"
Source="radioButtonChecked.png"
WidthRequest="18"/>
<Image
HeightRequest="18"
IsVisible="{Binding IsUnselected}"
Source="radioButtonUnchecked.png"
WidthRequest="18"/>
<Label
Margin="8,0,0,0"
Text="{Binding Caption}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
We create a listview in our content page and listen for the ItemSelected event. Each list item is a horizontal stack panel where we flip between two images depending on the selected state
Code Behind
public partial class LoginPage : ContentPage
{
LoginPageViewModel LoginPageViewModel { get; }
public LoginTwoFactorFrequencyPage ()
{
BindingContext = LoginPageViewModel = new LoginPageViewModel();
InitializeComponent ();
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
LoginPageViewModel.UpdateSelected(e.SelectedItem as PersistenceDuration);
}
}
The page's code behind instantiates a view model and calls an UpdateSelected method with the newly selected item on the page's view model*
RadioButton ViewModel
The view model for each radio button:
public class PersistenceDuration : ViewModelBase
{
bool isSelected;
public string Caption { get; set; }
public TwoFactorTokenPersistenceDuration Duration { get; set; }
public bool IsSelected
{
get => isSelected;
set
{
isSelected = value;
OnPropertyChanged();
OnPropertyChanged("IsUnselected");
}
}
public bool IsUnselected => !IsSelected;
public PersistenceDuration(string caption, TwoFactorTokenPersistenceDuration duration)
{
Caption = caption;
Duration = duration;
IsSelected = false;
}
}
The radio button view model holds selection info and the caption. We make sure to fire OnPropertyChanged whenever the selected state changes
Page ViewModel
public class LoginPageViewModel : ViewModelBase
{
PersistenceDuration duration;
PersistenceDuration selectedDuration;
public ObservableCollection<PersistenceDuration> Durations { get; }
public PersistenceDuration SelectedDuration
{
get => selectedDuration;
set
{
if (value != null)
{
duration = value;
UpdateSelected(duration);
}
OnPropertyChanged();
}
}
public LoginTwoFactorFrequencyViewModel()
{
Durations = new ObservableCollection<PersistenceDuration>(
new List<PersistenceDuration>()
{
new PersistenceDuration(AppResources.Save_code__forever, TwoFactorTokenPersistenceDuration.Forever),
new PersistenceDuration(AppResources.ChatRequireEvery30Days, TwoFactorTokenPersistenceDuration.ThirtyDays),
new PersistenceDuration(AppResources.ChatRequireEveryLogin, TwoFactorTokenPersistenceDuration.None),
});
}
public void UpdateSelected(PersistenceDuration persistenceDuration)
{
foreach (var item in Durations)
item.IsSelected = persistenceDuration == item;
}
}
In the page view model we create a list of radio button view models that the XAML binds to. When we UpdateSelected() all the IsSelected states are updated which trigger binding updates which flip the image.
You will still need to do something about the highlight when someone selects an item, but that is easy enough to find on the internet :)
You can use XLabs plugin from manage NuGets package. After installing you can use like this:
In Xaml:
controls:BindableRadioGroup x:Name="Radiobtn"
In C#:
string[] gender = {"MAlE","FEMALE"}
Radiobtn.Add(gender)
Refer Link
https://github.com/XLabs/Xamarin-Forms-Labs/tree/master/samples/XLabs.Samples/XLabs.Samples/Pages/Controls
You can get the radio button effect without a package. Use Labels with text unicode circle \u26AA or \u25CB. Attach a tab gesture recognizer to each label.
When tapped, change the text of the selected button to unicode circle bullet \u29BF and change the text of the other button(s) back to unicode circle \u26AA.
Test on your preferred platforms as each platform may display somewhat differently. You may need to adjust the font size as you change the text.
If you want real radiobuttons you can xlabs their package (https://github.com/XLabs/Xamarin-Forms-Labs/tree/master/src/Forms/XLabs.Forms/Controls/RadioButton)
Personally I'd just use a picker, Xlabs package hasn't been updated in a while so their might be some bugs in the radiobutton
You can use image as a radio button. When tou you click on it, it can change. It is not a good way to do it though.
This is xaml code:
<Image Scale="0.7" HorizontalOptions="Start" x:Name="radioButton" Source="unRadioBtn.png">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="radioButton_Clicked"></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
And this is .cs:
private void radioButton_Clicked(object sender, EventArgs e)
{
radioButton.Source = "radioBtn.png";
}
Xamarin.Forms 4.6 introduced a new RadioButton control. You can find the documentation here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/radiobutton
You can use the switch component. Also you can see the implementation for a checkbox component from the XLabs project which is now discontinued, get the code and modify it as you need.
Hint: You're gonna need the custom renderers per platform for it to work .
You need to use Picker
https://developer.xamarin.com/api/type/Xamarin.Forms.Picker/
Actually it is the best alternative to RadionButton On Xamarin.forms
XLabs RadioButton and BindableRadioGroup work well: XLabs RadioButton for Xamarin Forms
Here's a simple Yes/No radio using the BindableRadioGroup:
var answers = new List<string>();
answers.Add("Yes");
answers.Add("No");
var RadioGroup = new XLabs.Forms.Controls.BindableRadioGroup()
{
ItemsSource = answers,
Orientation = StackOrientation.Horizontal
};
Xamarin Forms now provides a Radio Button control.
See docs here:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/radiobutton
As of XF 4.8 this is still experimental and I've not yet used this feature so can't comment on its stability.

How to swap images based on the current theme WP7

I've failed certification because I have an image that's all white. So when a user switches to the light theme it fails because you can't see it. How do I swap this imaged based on the theme applied?
Thank you in advance
Question Making an image control invert its colors depending on theme has a simple XAML only answer:
<Image HorizontalAlignment="Center"Stretch="None" Visibility="{StaticResource PhoneLightThemeVisibility}" Source="/MyApplication;component/imageDarkTheme.png" />
<Image HorizontalAlignment="Center" Stretch="None" Visibility="{StaticResource PhoneDarkThemeVisibility}"Source="/MyApplication;component/imageLightTheme.png" />
In XAML
<Image Source="{Binding ImageSource}" />
In the view model that contains property ImageSource
public string ImageSource
{
get
{
if( (Visibility)App.Current.Resources["PhoneDarkThemeVisibility"]
== Visibility.Visible ) {
return "/path/to/dark/image.png";
} else {
return "/path/to/light/image.png";
}
}
private set {}
}
This may not change the picture if the user tombstones your app, changes the theme and switches back to the app.
One way to handle that scenario is to cache the current theme setting in the App class constructor, then compare it with the current setting within App.Application_Activated, if they're different you'll need to somehow indicate that the above view model needs to fire a property changed notification for ImageSource.
For anyone following my comments above - I had to switch from ImageBrush to Image directly (xaml below)
<Button Tag="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="-10,20,0,0" BorderThickness="0" Width="105" Height="102" Click="ShowKioskOnMap_Click">
<Image Source="../images/arrow.png" Width="55" Height="53" ImageOpened="Image_ImageOpened"/>
</Button>
Next in the consructor of the page I'm using --I did the theme lookup to prevent any issues when the app state is restored from taking a phone call for instance (or just loading up the view for the first time)
InitializeComponent();
theme = ""; //field level var (could make it dark by default if needed)
if ((Visibility)App.Current.Resources["PhoneDarkThemeVisibility"] == Visibility.Visible)
{
theme = "dark";
}
else
{
theme = "light";
}
And I had to implement the following in my open event to switch based on theme
private void Image_ImageOpened(object sender, RoutedEventArgs e)
{
var brush = (sender as Image);
if (brush.Stretch == Stretch.Uniform)
{
if (theme == "light")
brush.Source = new BitmapImage(new Uri("../images/arrowLight.png", UriKind.Relative));
brush.Stretch = Stretch.Fill;
}
}

Resources