Position View below RelativeLayout - xamarin

Context of the problem:
I do have a StackLayout with a lot of entries. When the user taps on an entry I do want to show below the tapped entry an info box. This info box should visually be above the next entry (kind of like a tooltip). The entry can have a dynamic height.
What is my approach:
Using a RelativeLayout it should be possible to position views outside the bounds of the RelativeLayout which represents the entry.
Something like this:
<StackLayout>
<BoxView BackgroundColor="Green" HeightRequest="150" ></BoxView>
<RelativeLayout BackgroundColor="Yellow" x:Name="container">
<Label Text="This is the entry"></Label>
<BoxView BackgroundColor="Aqua"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=container, Property=Y, Factor=1, Constant=100}"></BoxView>
</RelativeLayout>
<BoxView BackgroundColor="Green" HeightRequest="150" ></BoxView>
</StackLayout>
In this sample code the green BoxView's are kind of the entries before and after the one I do want to show. This is the result:
This makes actually sense, as I've linked to the Y-Property of the container and added 100 using "Constant".
And this is what I do want to archive:
I want to have a StackLayout with multiple entries. Whenever I click on one of this entries (yellow) right below an info should appear (blue).
How do I have to specify the YConstraint on the BoxView (which should illustrate the info window) to archive my goal? Or am I on a wrong path and another solution fits better?

I write a demo about your needs, here is running GIF.
First of all, I create content view.
<ContentView.Content>
<RelativeLayout x:Name="container" BackgroundColor="Yellow">
<Entry Text="This is the entry" x:Name="MyEntry" Focused="MyEntry_Focused" Unfocused="MyEntry_Unfocused">
</Entry>
</RelativeLayout>
</ContentView.Content>
Here is background code about content view.
public partial class FloatEntry : ContentView
{
BoxView boxView;
public FloatEntry()
{
InitializeComponent();
boxView = new BoxView();
boxView.BackgroundColor = Color.Red;
boxView.WidthRequest = 200;
}
private void MyEntry_Focused(object sender, FocusEventArgs e)
{
container.Children.Add(boxView,Constraint.RelativeToView(MyEntry, (Parent, sibling) =>
{
return sibling.X + 100;
}), Constraint.RelativeToView(MyEntry, (parent, sibling) =>
{
return sibling.Y + 50;
}));
container.RaiseChild(boxView);
}
private void MyEntry_Unfocused(object sender, FocusEventArgs e)
{
container.Children.Remove(boxView);
}
}
}
But If you used this way to achieve it, you want to BoxView to cover the below Entry. You have to put the content view to a RelativeLayout as well.
<RelativeLayout x:Name="myRl">
<myentry:FloatEntry x:Name="myfloat" HorizontalOptions="StartAndExpand" HeightRequest="50" >
<myentry:FloatEntry.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</myentry:FloatEntry.GestureRecognizers>
</myentry:FloatEntry>
<myentry:FloatEntry HorizontalOptions="StartAndExpand" HeightRequest="50"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=myfloat, Property=Y, Factor=1, Constant=50}"
>
</myentry:FloatEntry>
</RelativeLayout>
Here is layout background code.
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
// I need to use following method to move the Boxview cover the blew Entry
myRl.RaiseChild(myfloat);
}
}

A more generic approach would be to write your own control which could be named as InfoBoxPopup (bascially a ContentPage) which you open manually once the Entry gets Focused and Close it on Unfocus.
Just be sure that you have on top of every page a grid panel defined.
In the InfoBox.xaml you define your custom style (panel, label, margins, IsInputTransparent?, etc. to show the custom text or other stuff)
public partial class InfoBoxPopup : ContentView
{
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(InfoBoxPopup));
public InfoBoxPopup()
{
InitializeComponent();
}
public string? Text
{
get => (string?)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public void Show()
{
var rootGrid = GetCurrentPageGrid();
var rowsCount = rootGrid.RowDefinitions.Count;
if (rowsCount > 1)
{
Grid.SetRowSpan(this, rowsCount);
}
rootGrid.Children.Add(this);
}
public void Close()
{
var rootGrid = (Grid)Parent;
rootGrid.Children.Remove(this);
}
private static Grid GetCurrentPageGrid()
{
var shellView = (ShellView)Application.Current.MainPage;
var contentPage = (ContentPage)shellView.CurrentPage;
if (contentPage.Content is Grid grid) { return grid; }
var actualPanel = contentPage.Content;
for (int i = 0; i < 10; i++)
{
var children = actualPanel.LogicalChildren;
var childGrid = children.OfType<Grid>().FirstOrDefault();
if (childGrid != null) { return childGrid; }
actualPanel = children.OfType<View>().FirstOrDefault();
}
throw new ArgumentException("No Grid panel could identified to place the info box!");
}
}

Related

[UWP How to get control of a UserControl placed inside a ListView without altering the listview binding source?

I have placed a UserControl inside a ListView.
How do I get the control of this UserControl in the view.
If I place it inside a ListView, I am unable to access it in the view. I also do not wish to make any changes to the listView binding source.
Its name isn't accessible directly in the view.
I am able to access the events but not Properties(x:Name , Visibility etc..).
You can use VisualTreeHelper class to get your UserControl .
Get each ListViewItem by calling the ListView's ContainerFromItem or ContainerFromIndex.
Create a recursive function to find the DependencyObjects that are in each ListViewItem as a UserControl.
I made a simple to show how it works. You can refer to the following code.
MainPage.xaml
<Grid>
<ListView x:Name="MyListView" Margin="0,0,0,109">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Grid>
<local:MyUserControl1></local:MyUserControl1>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Button" Margin="682,943,0,0" VerticalAlignment="Top" Click="Button_Click"/>
</Grid>
MainPage.cs
public List<string> ItemsSourceList { get; set; }
public MainPage()
{
this.InitializeComponent();
ItemsSourceList = new List<string>();
ItemsSourceList.Add("1");
ItemsSourceList.Add("2");
ItemsSourceList.Add("3");
ItemsSourceList.Add("4");
ItemsSourceList.Add("5");
MyListView.ItemsSource = ItemsSourceList;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach (var strItem in ItemsSourceList)
{
// get every listview item first
ListViewItem item = MyListView.ContainerFromItem(strItem) as ListViewItem;
// the DependencyObject is the UserControl that you want to get
DependencyObject myUserControl = FindChild(item);
}
}
public DependencyObject FindChild(DependencyObject parant)
{
int count = VisualTreeHelper.GetChildrenCount(parant);
for (int i = 0; i < count; i++)
{
var MyChild = VisualTreeHelper.GetChild(parant, i);
if (MyChild is MyUserControl1)
{
//Here can get the MyUserControl1.
MyUserControl1 myUserControl = (MyUserControl1)MyChild;
myUserControl.Foreground = new SolidColorBrush(Colors.Red);
return myUserControl;
}
else
{
var res = FindChild(MyChild);
return res;
}
}
return null;
}

Best approach for show/hide password toggle functionality in Xamarin traditional approach

We are working on show/ hide password toggle functionality in Xamarin traditional approach. What is the best place to implement it? Is it in Xamarin.iOS &. Droid or in Xamarin.Core?
If it is in Xamarin.Core, can you let us know the process. Is it by value convertors?
Thanks in advance.
Recently, Microsoft MVP Charlin, wrote an article showing how to do this using Event Triggers in the Xamarin Forms code:
She was able to do it simply using a new ShowPasswordTriggerAction of type TriggerAction that implemented INotifyPropertyChanged.
Therein, she created a HidePassword bool property that Invoke a PropertyChanged event which changes the Source of the Icon image:
protected override void Invoke(ImageButton sender)
{
sender.Source = HidePassword ? ShowIcon : HideIcon;
HidePassword = !HidePassword;
}
Then place the Entry and ImageButton inside a layout (like a Frame or horizontally oriented LinearLayout) as shown:
<Entry Placeholder="Password"
IsPassword="{Binding Source={x:Reference ShowPasswordActualTrigger}, Path=HidePassword}"/>
<ImageButton VerticalOptions="Center"
HeightRequest="20"
HorizontalOptions="End"
Source="ic_eye_hide">
<ImageButton.Triggers>
<EventTrigger Event="Clicked">
<local:ShowPasswordTriggerAction ShowIcon="ic_eye"
HideIcon="ic_eye_hide"
x:Name="ShowPasswordActualTrigger"/>
</EventTrigger>
</ImageButton.Triggers>
</ImageButton>
We always use custom controls to show/hide password while entering the password using effects.
Android:
Create the control manually in ‘OnDrawableTouchListener’ method where, we are adding the ShowPass and HidePass icons to the entry control, changing them on the basis of user touch action and attaching it on effect invocation which will be fired when the effect is added to the control.
public class OnDrawableTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
if (v is EditText && e.Action == MotionEventActions.Up)
{
EditText editText = (EditText)v;
if (e.RawX >= (editText.Right - editText.GetCompoundDrawables()[2].Bounds.Width()))
{
if (editText.TransformationMethod == null)
{
editText.TransformationMethod = PasswordTransformationMethod.Instance;
editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, Resource.Drawable.ShowPass, 0);
}
else
{
editText.TransformationMethod = null;
editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, Resource.Drawable.HidePass, 0);
}
return true;
}
}
return false;
}
}
Result:
IOS:
Create the control manually in 'ConfigureControl' method where we are adding the ShowPass and HidePassicons to the entry control, changing them on the basis of user touch action; and attaching it on effect invocation which will be fired when the effect will be added to the control.
private void ConfigureControl()
{
if (Control != null)
{
UITextField vUpdatedEntry = (UITextField)Control;
var buttonRect = UIButton.FromType(UIButtonType.Custom);
buttonRect.SetImage(new UIImage("ShowPass"), UIControlState.Normal);
buttonRect.TouchUpInside += (object sender, EventArgs e1) =>
{
if (vUpdatedEntry.SecureTextEntry)
{
vUpdatedEntry.SecureTextEntry = false;
buttonRect.SetImage(new UIImage("HidePass"), UIControlState.Normal);
}
else
{
vUpdatedEntry.SecureTextEntry = true;
buttonRect.SetImage(new UIImage("ShowPass"), UIControlState.Normal);
}
};
vUpdatedEntry.ShouldChangeCharacters += (textField, range, replacementString) =>
{
string text = vUpdatedEntry.Text;
var result = text.Substring(0, (int)range.Location) + replacementString + text.Substring((int)range.Location + (int)range.Length);
vUpdatedEntry.Text = result;
return false;
};
buttonRect.Frame = new CoreGraphics.CGRect(10.0f, 0.0f, 15.0f, 15.0f);
buttonRect.ContentMode = UIViewContentMode.Right;
UIView paddingViewRight = new UIView(new System.Drawing.RectangleF(5.0f, -5.0f, 30.0f, 18.0f));
paddingViewRight.Add(buttonRect);
paddingViewRight.ContentMode = UIViewContentMode.BottomRight;
vUpdatedEntry.LeftView = paddingViewRight;
vUpdatedEntry.LeftViewMode = UITextFieldViewMode.Always;
Control.Layer.CornerRadius = 4;
Control.Layer.BorderColor = new CoreGraphics.CGColor(255, 255, 255);
Control.Layer.MasksToBounds = true;
vUpdatedEntry.TextAlignment = UITextAlignment.Left;
}
}
Result:
For more details, please refer to the article below.
https://www.c-sharpcorner.com/article/xamarin-forms-tip-implement-show-hide-password-using-effects/
You could download the source file from GitHub for reference.
https://github.com/techierathore/ShowHidePassEx.git
You can use the PhantomLib library to do this. It has a control which allows you to have a show/hide icon for the password with examples. Just install the nuget. https://github.com/OSTUSA/PhantomLib
Your UI codes like this having a entry and image button
source to named accroding to your ui
<Frame CornerRadius="30" Background="white" Padding="0" HeightRequest="43" Margin="0,17,0,0">
<StackLayout Orientation="Horizontal">
<Entry x:Name="eLoginPassword"
Margin="15,-10,0,-15"
HorizontalOptions="FillAndExpand"
IsPassword="True"
Placeholder="Password"/>
<ImageButton
x:Name="ibToggleLoginPass"
Clicked="IbToggleLoginPass"
Source="eyeclosed"
Margin="0,0,13,0"
BackgroundColor="White"
HorizontalOptions="End"
/>
</StackLayout>
</Frame>
in C# code
// IbToggleLoginPass your defined method in xaml
//"eye" is drawable name for open eye and "eyeclosed" is drawable name for closed eye
private void IbToggleLoginPass(object sender, EventArgs e)
{
bool isPass = eLoginPassword.IsPassword;
ibToggleLoginPa`enter code here`ss.Source = isPass ? "eye" : "eyeclosed";
eLoginPassword.IsPassword = !isPass;
}
Trigger and a command
The trigger changes the icon, and the command changes the entry.
View xaml
<Grid>
<Entry Placeholder="Password" Text="{Binding Password, Mode=TwoWay}" IsPassword="{Binding IsPassword}" />
<ImageButton BackgroundColor="Transparent" WidthRequest="24" VerticalOptions="Center" TranslationY="-5" TranslationX="-10" HorizontalOptions="End"
Command="{Binding ToggleIsPassword}"
Source="eye" >
<ImageButton.Triggers>
<DataTrigger TargetType="ImageButton" Binding="{Binding IsPassword}" Value="True" >
<Setter Property="Source" Value="eye-slash" />
</DataTrigger>
</ImageButton.Triggers>
</ImageButton>
</Grid>
And in my ViewModel
private bool _IsPassword = true;
public bool IsPassword
{
get
{
return _IsPassword;
}
set
{
_IsPassword = value;
RaisePropertyChanged(() => IsPassword);
}
}
public ICommand ToggleIsPassword => new Command(() => IsPassword = !IsPassword);

How to create a Xamarin Tooltip in code-behind

I am testing using the following example. https://github.com/CrossGeeks/TooltipSample
The sample works fine, it even works with Labels (sample uses buttons, images and boxviews). The issue is in my main App I need to create the tooltips in code behind.
To test how to do it, in the very same solution (from that above example) I created a TestPage and made it my MainPage in App.xaml.cs. The XAML looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ToolTipSample.TestPage">
<ContentPage.Content>
<StackLayout
x:Name="mainLayout"
BackgroundColor="Yellow">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="Handle_Tapped"/>
</StackLayout.GestureRecognizers>
</StackLayout>
</ContentPage.Content>
The code-behind looks like this:
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using ToolTipSample.Effects;
namespace ToolTipSample
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
var actionLabel = new Label
{
Text = "Show Tooltip",
WidthRequest = 150,
VerticalOptions = LayoutOptions.StartAndExpand,
HorizontalOptions = LayoutOptions.Center,
BackgroundColor = Color.Wheat
};
// Add tooltip to action label
TooltipEffect.SetPosition(actionLabel, TooltipPosition.Bottom);
TooltipEffect.SetBackgroundColor(actionLabel, Color.Silver);
TooltipEffect.SetTextColor(actionLabel, Color.Teal);
TooltipEffect.SetText(actionLabel, "This is the tooltip");
TooltipEffect.SetHasTooltip(actionLabel, true);
actionLabel.Effects.Add(Effect.Resolve($"CrossGeeks.{nameof(TooltipEffect)}"));
mainLayout.Children.Add(actionLabel);
}
void Handle_Tapped(object sender, System.EventArgs e)
{
foreach (var c in mainLayout.Children)
{
if (TooltipEffect.GetHasTooltip(c))
{
TooltipEffect.SetHasTooltip(c, false);
TooltipEffect.SetHasTooltip(c, true);
}
}
}
}
}
All other code unchanged.
When I tap the label, the tooltip appears as expected. But when I tap the background it does not disappear (like those created in XAML in the sample).
One other thing. If I tap twice it disappears.
Can anyone see what I am missing?
Thanks.
According to your description and code, you can delete the following line code to achieve your requirement.
actionLabel.Effects.Add(Effect.Resolve($"CrossGeeks.{nameof(TooltipEffect)}"));
You don't need to add effect for control when page load, because this effect will be added when you click this control by these code:
static void OnHasTooltipChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
bool hasTooltip = (bool)newValue;
if (hasTooltip)
{
view.Effects.Add(new ControlTooltipEffect());
}
else
{
var toRemove = view.Effects.FirstOrDefault(e => e is ControlTooltipEffect);
if (toRemove != null)
{
view.Effects.Remove(toRemove);
}
}
}

How to show Navigationbar 'TitleView' Common for all pages

I have added TitleView in MainPage to show on Navigationbar but it shows only for MainPage when I navigate to some other page Navigationbar displaying empty.
Below code I have in MainPage.xaml file
<NavigationPage.TitleView>
<RelativeLayout HorizontalOptions="Fill"
<Image Source="bell.png" HeightRequest="25" WidthRequest="25" x:Name="imgBell"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,
Property=Height,
Factor=0.018,Constant=10}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding GetStaffAnnouncementCommand}"></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
<Label FontSize="10" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" BackgroundColor="Transparent" Text="2" TextColor="Red"
HeightRequest="22" WidthRequest="23" x:Name="labelText">
</Frame>
</RelativeLayout>
</NavigationPage.TitleView>
When I click on bell icon and move to second page TitleView not displaying at all
How can I display TitleView common for all pages?
I wrote a demo about your needs.
There is GIF.
You could Write a custom page inherits from 'ContentPage' and add toolbar item to it.
Update
I achieve it with DependencyService, If you want to know more details about how to achieve DependencyService, you could refer to this blog and my code.
https://www.xamboy.com/2018/03/08/adding-badge-to-toolbaritem-in-xamarin-forms/
There is code that used DependencyService.
Custom ToolbarPage
public class ToolbarPage : ContentPage
{
public ToolbarItem toolbarItem;
public static int item;
public ToolbarPage()
{
// public ToolbarItem(string name, string icon, Action activated, ToolbarItemOrder order = ToolbarItemOrder.Default, int priority = 0);
toolbarItem =new ToolbarItem();
toolbarItem.Icon = "ring2.png";
toolbarItem.Order = ToolbarItemOrder.Primary;
// toolbarItem.Text = item+"";
toolbarItem.Priority = 0;
toolbarItem.Clicked += ToolbarItem_Clicked;
ToolbarItems.Add(toolbarItem);
if (item >= 1)
{
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, toolbarItem, $"{item}", Color.Red, Color.White);
}
}
private void ToolbarItem_Clicked(object sender, EventArgs e)
{
item = item + 1;
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, toolbarItem, $"{item}", Color.Red, Color.White);
}
}
Main.cs
public partial class MainPage : ToolbarPage
{
public MainPage()
{
InitializeComponent();
bt1.Text = ToolbarPage.item.ToString();
bt1.Clicked += async (o, e) =>
{
await Navigation.PushAsync(new HelloToolbarInher());
};
}
protected override async void OnAppearing()
{
//You must make a delay,
await Task.Delay(100);
bt1.Text = ToolbarPage.item.ToString();
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, toolbarItem, $"{ToolbarPage.item}", Color.Red, Color.White);
}
}
Do not forget to change MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<local:ToolbarPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:NaviagationViewDemo"
x:Class="NaviagationViewDemo.MainPage">
<StackLayout>
<!-- Place new controls here -->
<Button
x:Name="bt1"
Text="click"
></Button>
</StackLayout>
There is my new demo.
https://github.com/851265601/NewNaviagationViewDemo

Xamarin.Forms issue with button with border?

I have this button:
<Button x:Name="btnNext" BorderWidth="2" BorderColor="#96AF5B" BorderRadius="4"
WidthRequest="110" HeightRequest="25" Padding="0" VerticalOptions="Center" HorizontalOptions="Center"
BackgroundColor="#FFFCFF" FontSize="Default"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Width,Factor=0.5, Constant=-55}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView,
ElementName=videoPlayer,Property=Height,Factor=0.85, Constant=12.5}" FontFamily="verdana"
Clicked="Next_Clicked"/>
in Android, it shows a little square at the top left side of the button when tapped, this also happens when using a frame instead of setting the button's border properties.
here's a gif:
https://i.stack.imgur.com/FGv5j.gif
Actually, this bug has been around for a while now if I am not wrong we started facing this issue around somewhere in mid of March and it has been there ever since.
If you check Bugzilla there are a ton of bugs that have been logged for all the issues that people are facing because of this:
https://bugzilla.xamarin.com/show_bug.cgi?id=58140
https://bugzilla.xamarin.com/show_bug.cgi?id=42351
https://bugzilla.xamarin.com/show_bug.cgi?id=60248
https://bugzilla.xamarin.com/show_bug.cgi?id=60392
So I went out and devised a workaround which seems to be working fine for us using Label and Stack layout with some customized changes:
public class CustomButton: Label
{
public static readonly BindableProperty CommandProperty =
BindableProperty.Create("Command", typeof(ICommand), typeof(CustomButton), null);
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create("CommandParameter", typeof(object), typeof(CustomButton), null);
public event EventHandler ItemTapped = ( e, a ) => { };
public CustomButton()
{
Initialize();
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
private ICommand TransitionCommand
{
get
{
return new Command(async () =>
{
AnchorX=0.48;
AnchorY=0.48;
await this.ScaleTo(0.8, 50, Easing.Linear);
await Task.Delay(100);
await this.ScaleTo(1, 50, Easing.Linear);
Command?.Execute(CommandParameter);
ItemTapped(this, EventArgs.Empty);
});
}
}
public void Initialize()
{
GestureRecognizers.Add(new TapGestureRecognizer
{
Command=TransitionCommand
});
}
}
I have also added a little animation so it gives the feel for a button.
Then use this Label as follows:
<StackLayout BackgroundColor="Black" Padding="1"> // Here padding will be the border size you want and background color will be the color for it
<nameSpace:CustomButton XAlign="Center" BackgroundColor="Blue" /> //Height and Width request is mandatory here
</StackLayout>
The only problem with this solution is that you cannot add border-radius.

Resources