I want to implement something like below in the Xamarin forms.
I don't see such implementation with Objective C so can't find a way to render it through Native either.
How do I implement this with Xamarin forms?
https://demo.mobiscroll.com/javascript/list/display#
Working Solution: what follows will get your two-column UIPickerView rendered from Xamarin.Forms, belatedly updated from my original post
Option 1: Custom Renderer
I used the documentation #Junior Jiang shared in comments to inspire a quick Custom Renderer that should suit your needs.
First, make a placeholder control in your shared/Xamarin.Forms project:
namespace samples.core.Controls
{
public class MultiPickerControl : Xamarin.Forms.StackLayout
{
public MultiPickerControl()
{
// Just a placeholder
}
}
}
Then, make the renderer in your iOS project:
[assembly: ExportRenderer(typeof(samples.core.Controls.MultiPickerControl), typeof(samples.iOS.Controls.Picker.MultiPickerRenderer))]
namespace samples.iOS.Controls.Picker
{
public class MultiPickerRenderer : ViewRenderer
{
static UIPickerView pickerControl;
static DemoModel pickerModel = new DemoModel(new UILabel());
public MultiPickerRenderer()
{
pickerControl = new UIPickerView(
new CGRect(
UIScreen.MainScreen.Bounds.X - UIScreen.MainScreen.Bounds.Width,
UIScreen.MainScreen.Bounds.Height - 230,
UIScreen.MainScreen.Bounds.Width,
180))
{
Model = pickerModel
};
}
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if(Control != null)
{
Control.Add(pickerControl);
}
if(e.NewElement != null)
{
(e.NewElement as StackLayout).Children.Add(pickerControl);
}
}
}
}
You'll also need a UIPickerViewModel to give to your picker. This example is derived from the documentation above:
public class DemoModel : UIPickerViewModel
{
public Dictionary<string, string[]> options = new Dictionary<string, string[]>()
{
{ "America", new string[] { "Mexico", "USA" } },
{ "Europe", new string[] { "Germany", "France", "Italy"} },
{ "Asia", new string[] { "Korea", "Japan"} },
};
public override nint GetComponentCount(UIPickerView pickerView) => 2; // This determines how many columns are rendered
public override nfloat GetComponentWidth(UIPickerView picker, nint component) => component == 0 ? 120f : 160f;
public override nfloat GetRowHeight(UIPickerView picker, nint component) => 40f;
/// <summary>
/// Determines the number of rows to render in a component
/// </summary>
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
return options.Keys.Count;
else
{
var driver = pickerView.SelectedRowInComponent(0);
return options.Values.ElementAt((int)driver).Length;
}
}
/// <summary>
/// Gets the display value for a row in a component
/// </summary>
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
return options.Keys.ElementAt((int)row);
else
{
var driver = pickerView.SelectedRowInComponent(0);
return options.Values.ElementAt((int)driver).ElementAt((int)row);
}
}
[Export("pickerView:didSelectRow:inComponent:")]
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
// Update the display for column 2 if the value of column 1 has changed
if (component == 0)
{
pickerView.ReloadComponent(1);
pickerView.Select(0, 1, false);
}
}
Lastly, reference your placeholder control in the markup (or code behind). Xamarin will resolve the platform implementation when your code is run.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:samples.core.Controls;assembly=samples.core"
x:Class="samples.core.Views.EntryMoveNextView"
xmlns:ctrl="clr-namespace:samples.core.Controls;assembly=samples.core">
<ContentPage.Content>
<StackLayout Padding="15,25">
<controls:MultiPickerControl x:Name="MultiPicker"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The result should look like this:
Be sure to hook into the events available
I've added the above example to a samples repo on my GitHub, if you'd like to browse as a unified source.
Option 2: Native View
You also might be able to accomplish this with a Native View (documentation). The gist would look something 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"
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
x:Class="samples.core.Views.EntryMoveNextView"
xmlns:ctrl="clr-namespace:samples.core.Controls;assembly=samples.core">
<ContentPage.Content>
<StackLayout Padding="15,25">
<ios:UIPickerView>
<!-- Manipulate UIPickerView Properties Here -->
</ios:UIPickerView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
As with the linked documentation above, be sure to set GetComponentCount in your UIPickerViewModel to indicate how many columns you'll show.
Good luck!
Related
I want to declare a bindable property in my custom view and link it to the corresponding viewmodel.
I use the MVVM pattern and want to separate ui logic and data logic from eachother. So I keep my status and other data in the viewmodel and update my view according to viewmodel data changes.
This of course should be done by data binding.
Lets say I got the following xaml ...
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:MyApp.Views.Controls"
x:Class="MyApp.Views.Controls.MyView"
x:DataType="controls:MyViewVm">
<!--TODO: Content-->
</ContentView>
... with this code behind ...
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyApp.Views.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyView : ContentView
{
public static readonly BindableProperty StatusProperty = BindableProperty.Create(nameof(Status), typeof(MyStatus), typeof(MyView));
public MyStatus Status
{
get => (MyStatus)GetValue(StatusProperty);
set => SetValue(StatusProperty, value);
}
public MyView()
{
InitializeComponent();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
switch (propertyName)
{
case nameof(Status):
// TODO: Do styling ...
break;
}
}
}
}
... and this viewmodel and status enum:
namespace AbrechnungsApp.Views.Controls
{
public class MyViewVm : ViewModelBase
{
public MyStatus Status { get; set; }
}
public enum MyStatus
{
Enabled,
Disabled,
Readonly
}
}
Now the question is:
How do I link my viewmodels Status property to my views Status bindable property?
I typically create a helper property to cast BindingContext to the appropriate VM class:
private MyViewVm VM => (MyViewVm)BindingContext;
Then get/set VM properties in the bindable property:
public static readonly BindableProperty StatusProperty =
BindableProperty.Create(
nameof(Status), typeof(MyStatus), typeof(MyView),
propertyChanged: (binding, old, new) =>
{
// Needed if your XAML uses two-way binding.
Status = new;
});
public MyStatus Status
{
get => VM.Status;
set => VM.Status = value;
}
I've got a custom MyCachedImage that inherits from FFImageLoading.Forms.CachedImage, which is used in a ListView to display images.
The source of this image is composed by 2 properties: a custom object as entity and an integer as size.
Let's say if entity is a "city" object and size is 10 then the image source will be "http://..../city/10/image.png"
Image source must be setted only when both properties are valorized.
So, my answer is, how and when create the source url?
MyCachedImage.vb
public class MyCachedImage : CachedImage
{
public static readonly BindableProperty EntityProperty =
BindableProperty.Create(nameof(Entity), typeof(MyObject), typeof(MyCachedImage));
public MyObject Entity
{
get { return (MyObject)GetValue(EntityProperty); }
set { SetValue(EntityProperty, value); }
}
public static readonly BindableProperty SizeProperty =
BindableProperty.Create(nameof(Size), typeof(int), typeof(MyCachedImage), defaultValue: 0);
public int Size
{
get { return (int)GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
public MyCachedImage()
{
??? set source here?
}
protected override void OnBindingContextChanged()
{
??? set source here?
}
}
MyPage.xaml
<ListView ....>
....
<control:MyCachedImage Size="10"
Entity="{Binding MyObject}"
WidthRequest="40"
HeightRequest="40" />
....
</ListView>
I was wondering on when create that string and I found the right solution.
The OnBindingContextChanged is called when all properties are setted, so:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (_source == string.Empty)
{
Source = Helpers.ImageHelper.UriFromEntity(Entity, ImageSize);
}
}
I am very new to Xamarin Forms development and I need a popup dialog. I found exactly what I am looking for in https://github.com/rotorgames/Rg.Plugins.Popup, but I cannot for the life of me figure out how to use it. Could someone point me to a working example or provide some direction on use? The README.md on the site is not helping me much.
I want the the popup dialog to appear when a info button is clicked in the top navigation bar. All the popup needs is 1-2 buttons (and labels) for setting user settings.
This is for Xamarin.Forms: iOS and Android.
In simple steps:
Install the plugin in all the projects
Add the PopUp in your
Xaml
Use the methods they provide on the documentacion for Show/Hide the PopUp:
Task PushAsync(PopupPage page, bool animate = true)
Task PopAllAsync(bool animate = true)
They also provide a demo, check it:
https://github.com/rotorgames/Rg.Plugins.Popup/tree/master/src/Demo
Add a reference to the library, i.e. from nuget, to all projects.
Within your Android project, add this Rg.Plugins.Popup.Popup.Init(this, savedInstanceState); inside the MainActivity.cs OnCreate method, before Xamarin Forms Inits.
And the same for the iOS project, inside AppDelegate.cs FinishedLaunching method()
//Android
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Rg.Plugins.Popup.Popup.Init(this, savedInstanceState); /*Must add before the other Xamarin Inits*/
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState);
}
//iOS
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init(); /* place at the top*/
....
}
Add a new ContentPage (.xaml) to your Views directory.
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations; assembly=Rg.Plugins.Popup"
x:Class="MyProjectName.Views.MyContentPageName">
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="400"
DurationOut="300"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<StackLayout HorizontalAlignment="FillAndExpand" VerticalAlignment="FillAndExpand">
<!-- place your layout content here ....fx a close popup button -->
<Button Clicked="CloseBtn_Clicked" Text="Close" />
</StackLayout>
</pages:PopupPage>
In the ContentPage (PopupPage) code behind file, add using Rg.Plugins.Popup.Services; and inherit from the following
using Rg.Plugins.Popup.Services;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyContentPageName: Rg.Plugins.Popup.Pages.PopupPage
{
public MyContentPageName()
{
InitializeComponent();
}
public void OnAnimationStarted(bool isPopAnimation)
{
// optional code here
}
public void OnAnimationFinished(bool isPopAnimation)
{
// optional code here
}
protected override bool OnBackButtonPressed()
{
// Return true if you don't want to close this popup page when a back button is pressed
return true;
}
// Invoked when background is clicked
protected override bool OnBackgroundClicked()
{
// Return false if you don't want to close this popup page when a background of the popup page is clicked
return false;
}
private async void CloseBtn_Clicked(object sender, EventArgs e)
{
await PopupNavigation.Instance.PopAsync(true);
}
}
From the .xaml.cs page, where you would like to open the popup, add this:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Rg.Plugins.Popup.Contracts;
using Rg.Plugins.Popup.Services;
public partial class MyOtherPage : ContentPage
{
private IPopupNavigation _popup { get; set; }
private MyContentPageName _modalPage;
public MyOtherPage()
{
_popup = PopupNavigation.Instance;
_modalPage = new MyContentPageName();
}
protected override void OnAppearing()
{
base.OnAppearing();
_popup.Popped += Popup_Popped;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_popup.Popped -= Popup_Popped;
}
private async void Tapped_OpenModal(object sender, EventArgs e)
{
await _popup.PushAsync(_modalPage);
}
/// <summary> Triggered when the MyContentPageName popup is closed "PopAsync()" </summary>
private async void Popup_Popped(object sender, Rg.Plugins.Popup.Events.PopupNavigationEventArgs e)
{
/* add your logic here, if necessary */
}
}
*Note: If your modal simply displays static content, there is no need for a _popped event delegate within the OnAppearing()/OnDisappearing().
I've got Xamarin.Forms project with pcl-part and native win, ios and android parts.
All page structure and view-models are in pcl-part. App work's fine, but when I'm trying for example to hide Grid from code behind - it do nothing. Here is code example:
Xaml:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SomeNamespase.SomePage">
<Grid x:Name="InnerGrid" BackgroundColor="Green">
<Frame x:Name="InnerContent"/>
</Grid>
</ContentPage>
.cs :
using System;
namespace SomeNamespase
{
public partial class SomePage : ContentPage
{
public void SomeMethod()
{
this.InnerGrid.IsVisible = false;
this.InnerContent.BackgroundColor = Color.Aqua;
}
}
}
I've also tried this.FindByName<Grid>("InnerGrid"); the same result
Note: if I am trying to get controls from action in PCL everything is good. Nothing going on when I'm trying to get controls from ViewPresenter in windows (or other platforms) project.
You need to make sure you are properly implementing INotifyPropertyChanged
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Please try the below code, as in your code I can't see the constructor.
using System;
namespace SomeNamespase
{
public partial class SomePage : ContentPage
{
public SomePage()
{
SomeMethod() ;
}
public void SomeMethod()
{
this.InnerGrid.IsVisible = false;
this.InnerContent.BackgroundColor = Color.Aqua;
}
}
}
I am facing issue being a beginner for Xamarin forms and MVVM . I have tabbedpage and 2 pages are under tag . Here is code.
Issue is local:ActiveOrderViewPage page OnAppearing() event is firing twice when tabbedPage is loading and execute twice code under OnAppearing() event .
Please help me to find this why this is happening ?
Here Is code Tabbed Page
tabbedpage.xaml
<TabbedPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="Orders">
<TabbedPage.Children>
<local:ActiveOrderViewPage />
<local:SavedOrderViewPage />
</TabbedPage.Children>
tabbedpage.xaml.cs
public partial class OrderTabViewPage : TabbedPage
{
public OrderViewModel ViewModel { get { return BindingContext as OrderViewModel; } }
public OrderTabViewPage()
{
InitializeComponent();
this.BindingContext = ViewModelLocator.OrderVModel;
}
public OrderTabViewPage(params object[] arg) : this()
{
if (arg != null)
{
ViewModel.AccountID = Convert.ToInt32(arg[0]);
}
}
Here is active order .cs
public partial class ActiveOrderViewPage : ContentPage
{
public OrderViewModel ViewModel { get { return BindingContext as OrderViewModel; } }
public ActiveOrderViewPage()
{
InitializeComponent();
this.BindingContext = ViewModelLocator.OrderVModel;
}
//public OrderViewPage() : this()
//{
// ViewModel.AccountID = accuntId;
//}
protected override void OnAppearing()
{
base.OnAppearing();
if (ViewModelLocator.OrderVModel.ActiveOrderItems == null || ViewModelLocator.OrderVModel.ActiveOrderItems.List.Count == 0)
{
ViewModelLocator.OrderVModel.ActiveOrderCommand.Execute(null);
}
}
Thanks in advance ...
Having had this problem for a long long time, before realising, I know how frustrating this is. The event OnAppearing() fires twice because of the way that the tabbed page renders all of the individual pages. It initially renders the page, then in your case will render the other page, which causes the OnDisappearing() to fire. The first page then gets focus, causing the OnAppearing() to fire again.
If you only want the code to fire once, after adding the child pages, set the currentpage property to null, which should stop the OnAppearing() from firing again
In my case OnAppearing called twice while i had in app.xaml the follow code:
MainPage = new NavigationPage(new MainPage());
after i change the code to:
MainPage =new MainPage();
The OnAppearing is no more calling twice.