Xamarin Forms Maps Circle Custom Renderer - ios

Experiencing two issues with a custom map/renderer on iOS.
Demo video: https://ufile.io/pscn3
I have a custom map class with a circle that is placed on the map.
A slider control resizes the circle from a bindable property.
When slider value changes, the circle's radius property gets updated with the selected value. But as you can see, it's not updating the radius on map, instead it moves the circle to new positions within a curve.
When the circle is moved outside x pixels, it disappears or gets cut off outside the visible bounds.
These are the classes being used:
Page.xaml:
<?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:vm="clr-namespace:CompanyName.Data.ViewModels.MapWithCircleSlider;assembly=CompanyName"
xmlns:local="clr-namespace:CompanyName.UI;assembly=CompanyName"
x:Class="CompanyName.UI.Pages.MapWithCircleSlider"
Title="{Binding Title}">
<ContentPage.BindingContext>
<vm:MapWithCircleSliderViewModel></vm:MapWithCircleSliderViewModel>
</ContentPage.BindingContext>
<ContentPage.Content>
<ScrollView>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<local:CircleMap Grid.Row="0" CircleRadius="{Binding CircleRadius}" Latitude="{Binding Latitude}" Longitude="{Binding Longitude}" MapRadius="{Binding MapRadius}" IsShowingUser="true" HasZoomEnabled="true" />
<!--<Image Grid.Row="0" HorizontalOptions="Center" VerticalOptions="Center" Source="ic_place_green_48dp.png" />-->
<StackLayout Grid.Row="1" Padding="32,16">
<Entry VerticalOptions="Start" Placeholder="navn *" Text="{Binding Name}">
<Entry.Style>
<OnPlatform x:TypeArguments="Style">
<On Platform="iOS" Value="{x:Static local:Styling.IosEntryStyle}" />
</OnPlatform>
</Entry.Style>
</Entry>
<Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding CircleRadius, StringFormat='{0}m'}" />
<Slider VerticalOptions="End" Maximum="{Binding Maximum}" Minimum="{Binding Minimum}" Value="{Binding CircleRadius}" />
<!-- NB: Maximum must be set before Minimum, ref: https://bugzilla.xamarin.com/show_bug.cgi?id=23665 -->
</StackLayout>
</Grid>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Pages ViewModel:
using System;
using CompanyName.ViewModels;
namespace CompanyName.Data.ViewModels.MapWithCircleSlider
{
public class MapWithCircleSliderViewModel : ViewModelBase
{
private string name;
private int circleRadius;
private float latitude;
private float longitude;
private int mapRadius;
public MapWithCircleSliderViewModel()
{
Name = "Labs";
CircleRadius = 200;
MapRadius = 200;
Latitude = 58.9698634f;
Longitude = 5.7331874f;
}
public int Maximum => 1000;
public int Minimum => 100;
public string Id { get; set; }
public bool IsEditMode { get; set; }
public string Title { get; set; }
public string Name
{
get => name;
set
{
if (name == value) return;
name = value;
OnPropertyChanged("Name");
}
}
public int CircleRadius
{
get => circleRadius;
set
{
if (circleRadius == value) return;
circleRadius = value;
OnPropertyChanged("CircleRadius");
}
}
public float Latitude
{
get => latitude;
set
{
if (Math.Abs(latitude - value) < float.Epsilon) return;
latitude = value;
OnPropertyChanged("Latitude");
}
}
public float Longitude
{
get => longitude;
set
{
if (Math.Abs(longitude - value) < float.Epsilon) return;
longitude = value;
OnPropertyChanged("Longitude");
}
}
public int MapRadius
{
get => mapRadius;
set
{
if (mapRadius == value) return;
mapRadius = value;
OnPropertyChanged("MapRadius");
}
}
}
}
CircleMap.cs
using System.Diagnostics;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
namespace CompanyName.UI
{
public class CircleMap : Map
{
private const int DefaultCircleRadius = 100;
private const float DefaultLatitude = 58.8523208f;
private const float DefaultLongitude = 5.7326743f;
private const int DefaultMapRadius = 150;
public static readonly BindableProperty CircleRadiusProperty = BindableProperty.Create("CircleRadius", typeof(int), typeof(CircleMap), DefaultCircleRadius, BindingMode.TwoWay, propertyChanged: OnCircleRadiusPropertyChanged);
public static readonly BindableProperty LatitudeProperty = BindableProperty.Create("Latitude", typeof(float), typeof(CircleMap), DefaultLatitude, BindingMode.TwoWay, propertyChanged: OnLatitudePropertyChanged);
public static readonly BindableProperty LongitudeProperty = BindableProperty.Create("Longitude", typeof(float), typeof(CircleMap), DefaultLongitude, BindingMode.TwoWay, propertyChanged: OnLongitudePropertyChanged);
public static readonly BindableProperty MapRadiusProperty = BindableProperty.Create("MapRadius", typeof(int), typeof(CircleMap), DefaultMapRadius, BindingMode.TwoWay, propertyChanged: OnMapRadiusPropertyChanged);
public CircleMap() : base(MapSpan.FromCenterAndRadius(new Position(DefaultLatitude, DefaultLongitude), Distance.FromMeters(DefaultMapRadius))) { }
public int CircleRadius
{
get => (int)GetValue(CircleRadiusProperty);
set => SetValue(CircleRadiusProperty, value);
}
public float Latitude
{
get => (float)GetValue(LatitudeProperty);
set => SetValue(LatitudeProperty, value);
}
public float Longitude
{
get => (float)GetValue(LongitudeProperty);
set => SetValue(LongitudeProperty, value);
}
public int MapRadius
{
get => (int)GetValue(MapRadiusProperty);
set => SetValue(MapRadiusProperty, value);
}
private static void OnCircleRadiusPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var circleMap = (CircleMap)bindable;
circleMap.CircleRadius = (int)newValue;
}
private static void OnLatitudePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var circleMap = (CircleMap)bindable;
circleMap.Latitude = (float)newValue;
MoveToRegion(circleMap);
}
private static void OnLongitudePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var circleMap = (CircleMap)bindable;
circleMap.Longitude = (float)newValue;
MoveToRegion(circleMap);
}
private static void OnMapRadiusPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var circleMap = (CircleMap)bindable;
circleMap.MapRadius = (int)newValue;
MoveToRegion(circleMap);
}
private static void MoveToRegion(CircleMap circleMap)
{
circleMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(circleMap.Latitude, circleMap.Longitude), Distance.FromMeters(circleMap.MapRadius)));
}
}
}
CustomMapRenderer.cs (iOS):
using CompanyName.UI;
using MapKit;
using ObjCRuntime;
using System;
using System.ComponentModel;
using System.Linq;
using Xamarin.Forms;
using Xamarin.Forms.Maps.iOS;
using Xamarin.Forms.Platform.iOS;
using CompanyName.iOS.Renderers.CustomRenderer;
using CompanyName.Utilities;
[assembly: ExportRenderer(typeof(CircleMap), typeof(CustomMapRenderer))]
namespace CompanyName.iOS.Renderers.CustomRenderer
{
/// <remarks>
/// https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/map/circle-map-overlay/#Creating_the_Custom_Renderer_on_iOS
/// </remarks>
public class CustomMapRenderer : MapRenderer
{
private CircleMap circleMap;
private MKCircleRenderer circleRenderer;
private MKMapView NativeMap => Control as MKMapView;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
try
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
if (Control is MKMapView nativeMap)
{
nativeMap.RemoveOverlays(nativeMap.Overlays);
nativeMap.OverlayRenderer = null;
circleRenderer = null;
}
}
if (e.NewElement != null)
{
circleMap = (CircleMap)e.NewElement;
NativeMap.OverlayRenderer = GetOverlayRenderer;
AddOverlay();
}
}
catch (Exception ex)
{
//Logger.LogException(ex, GetType().Name);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (sender == null) return;
circleMap = (CircleMap)sender;
if (e.PropertyName == "VisibleRegion") OnVisibleRegionChanged();
if (e.PropertyName == CircleMap.CircleRadiusProperty.PropertyName) RedrawOverlay();
}
private MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
{
if (circleRenderer == null && !Equals(overlayWrapper, null))
{
var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
circleRenderer = new MKCircleRenderer(overlay as MKCircle)
{
Alpha = 0.15f,
FillColor = CompanyName.Constants.Colors.Skobeloff500.ToUIColor(),
LineWidth = 1,
StrokeColor = CompanyName.Constants.Colors.Skobeloff500.ToUIColor()
};
}
return circleRenderer;
}
private void OnVisibleRegionChanged()
{
SetNewCoordinates();
RedrawOverlay();
}
private void SetNewCoordinates()
{
circleMap.Latitude = (float)circleMap.VisibleRegion.Center.Latitude;
circleMap.Longitude = (float)circleMap.VisibleRegion.Center.Longitude;
circleMap.MapRadius = (int)circleMap.VisibleRegion.Radius.Meters;
}
private void RedrawOverlay()
{
RemoveOverlays();
AddOverlay();
}
private void RemoveOverlays()
{
if (NativeMap?.Overlays == null) return;
if (NativeMap.Overlays.Any()) NativeMap.RemoveOverlays(NativeMap.Overlays);
}
private void AddOverlay()
{
var circleOverlay = MKCircle.Circle(new CoreLocation.CLLocationCoordinate2D(circleMap.Latitude, circleMap.Longitude), circleMap.CircleRadius);
NativeMap.AddOverlay(circleOverlay);
}
}
}
Any feedback / suggestions are highly appreciated!

You can try to refresh the circleRenderer to achieve your effect like:
private void RemoveOverlays()
{
if (NativeMap?.Overlays == null) return;
if (NativeMap.Overlays.Any())
{
NativeMap.RemoveOverlays(NativeMap.Overlays);
circleRenderer = null;
NativeMap.OverlayRenderer = GetOverlayRenderer;
}
}

Related

Adding Searchview in Listview

I made an array to transfer the required data consisting of 3 elements(Heading,SubHeading, int ImageResourceId).
The data shows up well in listview but I couldn't do the research inside listview.
I want to do the research using searchview on specific SubHeading or both (Heading,SubHeading)
//on creat
listView.Adapter = new HomeScreenAdapter(this, HomeScreenAdapter.tableItems);
search.QueryTextChange += SearchV_QueryTextChange;
//filter listview
private void SearchV_QueryTextChange(object sender, SearchView.QueryTextChangeEventArgs e)
{
}
//
// 3-class listview item id
public class TableItem
{
public string Heading;
public string SubHeading;
public int ImageResourceId;
public TableItem(string Heading, string SubHeading, int ImageResourceId)
{
this.Heading = Heading;
this.SubHeading = SubHeading;
this.ImageResourceId = ImageResourceId;
}
}
}
//
//class adapter & array item
class HomeScreenAdapter : BaseAdapter<TableItem>
{
public static List<TableItem> tableItems = new List<TableItem>();
List<TableItem> items;
Activity context;
public void filldata()
{
tableItems.Add(new TableItem("hussein", "devepoer", Resource.Drawable.imaga));
tableItems.Add(new TableItem("ahmed", "admin", Resource.Drawable.imagb));
tableItems.Add(new TableItem("jasim", " manager", Resource.Drawable.imagc));
tableItems.Add(new TableItem("ahmed", "admin", Resource.Drawable.imagd));
tableItems.Add(new TableItem("jasim", " manager", Resource.Drawable.image));
tableItems.Add(new TableItem("jasim", " manager", Resource.Drawable.imagf));
}
}
According to your code, I do one sample by SubHeading to filter ListView data.
Firstly, create TableItem class:
public class TableItem
{
public string Heading { get; set; }
public string SubHeading { get; set; }
public int ImageResourceId { get; set; }
}
Then creating UI for ListView item:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView android:id="#+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ImageView android:id="#+id/imageView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
And create Adapter and filter for data:
public class TableItemAdapter : BaseAdapter<TableItem>, IFilterable
{
private List<TableItem> _originalData;
private List<TableItem> _items;
private readonly Activity _context;
public TableItemAdapter(Activity activity, IEnumerable<TableItem> tableitems)
{
_items = tableitems.OrderBy(s => s.SubHeading).ToList();
_context = activity;
Filter = new TableItemFilter(this);
}
public override TableItem this[int position]
{
get { return _items[position]; }
}
public Filter Filter { get; private set; }
public override int Count
{
get { return _items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var view = convertView ?? _context.LayoutInflater.Inflate(Resource.Layout.TableItem, null);
var tableitem = _items[position];
var HeadingView = view.FindViewById<TextView>(Resource.Id.textView1);
var SubHeadingView = view.FindViewById<TextView>(Resource.Id.textView2);
var imageView = view.FindViewById<ImageView>(Resource.Id.imageView1);
HeadingView.Text = tableitem.Heading;
SubHeadingView.Text = tableitem.SubHeading;
imageView.SetImageResource(tableitem.ImageResourceId);
return view;
}
public override void NotifyDataSetChanged()
{
// If you are using cool stuff like sections
// remember to update the indices here!
base.NotifyDataSetChanged();
}
private class TableItemFilter : Filter
{
private readonly TableItemAdapter _adapter;
public TableItemFilter(TableItemAdapter adapter)
{
_adapter = adapter;
}
protected override FilterResults PerformFiltering(ICharSequence constraint)
{
var returnObj = new FilterResults();
var results = new List<TableItem>();
if (_adapter._originalData == null)
_adapter._originalData = _adapter._items;
if (constraint == null) return returnObj;
if (_adapter._originalData != null && _adapter._originalData.Any())
{
// Compare constraint to all names lowercased.
// It they are contained they are added to results.
results.AddRange(
_adapter._originalData.Where(
item => item.SubHeading.ToLower().Contains(constraint.ToString())));
}
// Nasty piece of .NET to Java wrapping, be careful with this!
returnObj.Values = FromArray(results.Select(r => r.ToJavaObject()).ToArray());
returnObj.Count = results.Count;
constraint.Dispose();
return returnObj;
}
protected override void PublishResults(ICharSequence constraint, FilterResults results)
{
using (var values = results.Values)
_adapter._items = values.ToArray<Java.Lang.Object>().Select(r =>r.ToNetObject<TableItem>()).ToList();
_adapter.NotifyDataSetChanged();
// Don't do this and see GREF counts rising
constraint.Dispose();
results.Dispose();
}
}
}
ObjectExtensions to implement filter:
public class JavaHolder : Java.Lang.Object
{
public readonly object Instance;
public JavaHolder(object instance)
{
Instance = instance;
}
}
public static class ObjectExtensions
{
public static TObject ToNetObject<TObject>(this Java.Lang.Object value)
{
if (value == null)
return default(TObject);
if (!(value is JavaHolder))
throw new InvalidOperationException("Unable to convert to .NET object. Only Java.Lang.Object created with .ToJavaObject() can be converted.");
TObject returnVal;
try { returnVal = (TObject)((JavaHolder)value).Instance; }
finally { value.Dispose(); }
return returnVal;
}
public static Java.Lang.Object ToJavaObject<TObject>(this TObject value)
{
if (Equals(value, default(TObject)) && !typeof(TObject).IsValueType)
return null;
var holder = new JavaHolder(value);
return holder;
}
}
Mainactivity.cs:
public class MainActivity : AppCompatActivity
{
private SearchView searchview1;
private ListView listview1;
private TableItemAdapter tableitemadapter;
private List<TableItem> tableitems;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
searchview1 = FindViewById<SearchView>(Resource.Id.searchView1);
listview1 = FindViewById<ListView>(Resource.Id.listView1);
tableitems = new List<TableItem>()
{
new TableItem(){Heading= "hussein", SubHeading="devepoer",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "ahmed", SubHeading="admin",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "jasim", SubHeading="manager",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "cherry", SubHeading="admin",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "barry", SubHeading="manager",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "annine", SubHeading="manager",ImageResourceId= Resource.Drawable.plu3 }
};
tableitemadapter = new TableItemAdapter(this,tableitems);
listview1.Adapter = tableitemadapter;
searchview1.QueryTextChange += Searchview1_QueryTextChange;
}
private void Searchview1_QueryTextChange(object sender, SearchView.QueryTextChangeEventArgs e)
{
tableitemadapter.Filter.InvokeFilter(e.NewText);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
The sample in github:
https://github.com/CherryBu/Searchviewsample
The screenshot:

Xamarin forms - IOS issue - Content view nested on top of each other

I created a control in Xamarin forms that flips views(Like a card style). both these views have some sort of input (EX: List of buttons) that if you interact with will flip the "Card" control to the next view. I was able to get this working with Android, but when I test with IOS the controls seems to be disabled and I am not able to hit any events. Now I did solve a similar problem before by using the e.NativeView.UserInteractionEnabled property. The only issue is that this property can only be use when initializing the view, I want to be able to use something similar that is more dynamic.
A little progress on the IOS issue.
<?xml version="1.0" encoding="utf-8" ?>
<TemplatedView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="minto.qm.mobile.Views.Controls.PanelView">
<TemplatedView.ControlTemplate>
<ControlTemplate>
<AbsoluteLayout>
//the last item in this list will be the dominant control on runtime
//the other views input will not work
<ContentView Content="{TemplateBinding BackContent}" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
<ContentView Content="{TemplateBinding FrontContent}" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
</AbsoluteLayout>
</ControlTemplate>
</TemplatedView.ControlTemplate>
</TemplatedView>
Does anyone know of any functionality like this?
https://forums.xamarin.com/discussion/81114/how-to-create-a-custom-controls-with-multiple-view-on-top-of-each-other
update:
here is the behind code for the control:
public partial class PanelView : TemplatedView
{
public event EventHandler FrontContentAppeared;
public event EventHandler BackContentAppeared;
public static readonly BindableProperty FrontContentProperty = BindableProperty.Create(nameof(FrontContent), typeof(View), typeof(PanelView), defaultValue: null, propertyChanged: OnFrontContentChanged);
public static readonly BindableProperty BackContentProperty = BindableProperty.Create(nameof(BackContent), typeof(View), typeof(PanelView), defaultValue: null, propertyChanged: OnBackContentChanged);
public static readonly BindableProperty SwitchViewProperty = BindableProperty.Create(nameof(SwitchView), typeof(bool), typeof(PanelView), defaultValue: false, propertyChanged: OnSwitchViewChanged);
private bool _isFrontView = true;
public PanelView()
{
InitializeComponent();
}
private async void SwitchCurrentView()
{
if (_isFrontView)
{
BackContent.IsVisible = true;
//BackContent.InputTransparent = true;
FrontContent.Unfocus();
BackContent.Focus();
await Task.WhenAll(
FrontContent.FadeTo(0, 500, Easing.Linear),
BackContent.FadeTo(1, 500, Easing.Linear),
this.RotateYTo(GetRotation(0, 180), 250, Easing.Linear)
);
FrontContent.IsVisible = false;
//FrontContent.InputTransparent = false;
BackContentAppeared?.Invoke(this, EventArgs.Empty);
}
else
{
FrontContent.IsVisible = true;
//FrontContent.InputTransparent = true;
FrontContent.Focus();
BackContent.Unfocus();
await Task.WhenAll(
FrontContent.FadeTo(1, 500, Easing.Linear),
BackContent.FadeTo(0, 500, Easing.Linear),
this.RotateYTo(GetRotation(180, 180), 250, Easing.Linear)
);
BackContent.IsVisible = false;
//BackContent.InputTransparent = false;
FrontContentAppeared?.Invoke(this, EventArgs.Empty);
}
_isFrontView = !_isFrontView;
SwitchView = false;
}
private static void OnFrontContentChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SetFrontContentView((View)newValue);
}
private static void OnBackContentChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SetBackContentView((View)newValue);
}
private static void OnSwitchViewChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SwitchView = (bool)newValue;
if (self.SwitchView)
{
self.SwitchCurrentView();
}
}
private void SetFrontContentView(View view)
{
FrontContent = view;
if (!_isFrontView)
{
FrontContent.IsVisible = false;
view.FadeTo(0, 1, Easing.Linear);
}
}
private void SetBackContentView(View view)
{
view.FadeTo(0, 1, Easing.Linear);
view.RotateYTo(180, 1, Easing.Linear);
BackContent = view;
if (_isFrontView)
{
BackContent.IsVisible = false;
}
}
private double GetRotation(double start, double amount)
{
var rotation = (start + amount) % 360;
return rotation;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (FrontContent != null)
{
SetInheritedBindingContext(FrontContent, BindingContext);
}
if (BackContent != null)
{
SetInheritedBindingContext(BackContent, BindingContext);
}
}
public View FrontContent
{
get { return (View)GetValue(FrontContentProperty); }
set { SetValue(FrontContentProperty, value); }
}
public View BackContent
{
get { return (View)GetValue(BackContentProperty); }
set { SetValue(BackContentProperty, value); }
}
public bool SwitchView
{
get { return (bool)GetValue(SwitchViewProperty); }
set { SetValue(SwitchViewProperty, value); }
}
}
the animation runs when the bindable bool variable SwitchView changes
private void OnRoomTypeTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
UPDATE:
Xaml Page example. note: (Base:ExtendedPage is a custom contentpage) and (controls:Glider is are custom view with list of buttons on them)
<?xml version="1.0" encoding="utf-8" ?>
<base:ExtendedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:minto.qm.mobile.Views.Controls;assembly=minto.qm.mobile"
xmlns:base="clr-namespace:minto.qm.mobile.Views.Pages.Base;assembly=minto.qm.mobile"
x:Class="minto.qm.mobile.Views.Pages.RoomPage"
Navigation="{Binding Navigation, Mode=OneWayToSource}"
Title="Room">
<base:ExtendedPage.Content>
<ScrollView>
<StackLayout Spacing="0">
<ContentView BackgroundColor="#3498DB" Padding="10">
<Label Text="{Binding SelectedRoomName}" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"></Label>
</ContentView>
<StackLayout Spacing="5" Padding="10, 20, 10, 10">
<controls:PanelView x:Name="PanelView">
<controls:PanelView.FrontContent>
<controls:Glider ItemsSource="{Binding RoomTypes}" DisplayProperty="Name" SelectedIndex="0" ItemSelected="OnRoomTypeSelected" ItemTapped="OnRoomTypeTapped" Orientation="Vertical" Lines="3" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></controls:Glider>
</controls:PanelView.FrontContent>
<controls:PanelView.BackContent>
<controls:Glider ItemsSource="{Binding Rooms}" DisplayProperty="Name" SelectedIndex="0" ItemSelected="OnRoomSelected" ItemTapped="OnRoomTapped" Orientation="Horizontal" Lines="5" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></controls:Glider>
</controls:PanelView.BackContent>
</controls:PanelView>
<StackLayout Orientation="Horizontal">
<Label Text="Room annotation:" FontAttributes="Bold" Margin="5, 0, 0, 0" VerticalTextAlignment="Center"></Label>
<Button Text="<" FontAttributes="Bold" HorizontalOptions="EndAndExpand" VerticalOptions="Center" BackgroundColor="Transparent" WidthRequest="50" HeightRequest="50" Clicked="OnExpandButtonClicked" FontSize="Medium" AnchorX="0.5" AnchorY="0.5"></Button>
</StackLayout>
<Entry x:Name="AnnotationEditor" HorizontalOptions="FillAndExpand" BackgroundColor="#4D4D4D" TextChanged="Entry_OnTextChanged"/>
<Button Text="Next" Command="{Binding NextPageCommand}" HorizontalOptions="FillAndExpand" />
</StackLayout>
</StackLayout>
</ScrollView>
</base:ExtendedPage.Content>
<base:ExtendedPage.Overlay>
<controls:Tombstone Token="{Binding DeficiencyToken}" BackgroundColor="#444444"></controls:Tombstone>
</base:ExtendedPage.Overlay>
<base:ExtendedPage.ToolbarItems>
<ToolbarItem Text="Details" Order="Primary" Priority="0" Clicked="DetailsClicked"></ToolbarItem>
</base:ExtendedPage.ToolbarItems>
</base:ExtendedPage>
public partial class RoomPage : ExtendedPage
{
public ViewModels.Pages.RoomPage ViewModel => _vm ?? (_vm = BindingContext as ViewModels.Pages.RoomPage);
private ViewModels.Pages.RoomPage _vm;
private bool _buttonExpanded;
public RoomPage()
{
InitializeComponent();
BindingContext = new ViewModels.Pages.RoomPage();
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.OnAppearing();
HandleCaching();
}
private async void HandleCaching()
{
await Task.Run(() =>
{
var pageCache = Services.Caches.Pages.GetInstance();
pageCache.Preload(nameof(InspectionGalleryPage), new InspectionGalleryPage());
});
}
private void DetailsClicked(object sender, EventArgs e)
{
ShowOverlay = !ShowOverlay;
}
private void Entry_OnTextChanged(object sender, TextChangedEventArgs e)
{
ViewModel.RoomAnnotations = e.NewTextValue;
}
private void OnRoomTypeSelected(object sender, GliderItemSelectedEventArgs e)
{
ViewModel.SelectedRoomTypeName = e.Item.ToString();
}
private void OnRoomTypeTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
private void OnRoomSelected(object sender, GliderItemSelectedEventArgs e)
{
ViewModel.SelectedRoomName = e.Item.ToString();
}
private void OnRoomTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
private void AnimateHeight(View view, double end)
{
view.Animate("Expander", value => view.HeightRequest = value, view.Height, end, 2, 250, Easing.Linear);
}
private void OnExpandButtonClicked(object sender, EventArgs e)
{
var button = (Button) sender;
if (_buttonExpanded)
{
button.RotateTo(0, 250, Easing.Linear);
AnimateHeight(AnnotationEditor, 45.5);
}
else
{
button.RotateTo(-90, 250, Easing.Linear);
AnimateHeight(AnnotationEditor, 300);
}
_buttonExpanded = !_buttonExpanded;
}
}
As I said "something" is covering your button. Here is the problematic declaration
<AbsoluteLayout>
<ContentView Content="{TemplateBinding BackContent}" BackgroundColor="Transparent" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
<ContentView Content="{TemplateBinding FrontContent}" BackgroundColor="Transparent" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
</AbsoluteLayout>
Your FrontContent ContentView covers BackContent ContentView. When you rotate and hide content you actually working with buttons not views. I found this design a little more complicated than it should be but regardless that was not the question. Below is the solution. When you hide you content (meaning button) also hide your Parent view and show it when you show the button.
private async void SwitchCurrentView()
{
if (_isFrontView)
{
BackContent.IsVisible = true;
((ContentView)BackContent.Parent).IsVisible = true;//************************
FrontContent.Unfocus();
BackContent.Focus();
await Task.WhenAll(
FrontContent.FadeTo(0, 500, Easing.Linear),
BackContent.FadeTo(1, 500, Easing.Linear),
this.RotateYTo(GetRotation(0, 180), 250, Easing.Linear)
);
FrontContent.IsVisible = false;
((ContentView)FrontContent.Parent).IsVisible = false;//******************
BackContentAppeared?.Invoke(this, EventArgs.Empty);
}
else
{
FrontContent.IsVisible = true;
((ContentView)FrontContent.Parent).IsVisible = true;
FrontContent.Focus();
BackContent.Unfocus();
await Task.WhenAll(
FrontContent.FadeTo(1, 500, Easing.Linear),
BackContent.FadeTo(0, 500, Easing.Linear),
this.RotateYTo(GetRotation(180, 180), 250, Easing.Linear)
);
BackContent.IsVisible = false;
((ContentView)BackContent.Parent).IsVisible = false;
FrontContentAppeared?.Invoke(this, EventArgs.Empty);
}
_isFrontView = !_isFrontView;
SwitchView = false;
}

Can't get all values from web page

I am working with JSF 2 and Hibernate. I have XHTML page where bean is get from database and inserted into page. When I change the values of bean and submit for savign I am getting only the last value. What I making wrong?
Here is my XHTML page form code:
<h:form id="edit-slide-form">
<h:panelGrid columns="2">
<h:outputText value="#{msgs.slideTitle}" styleClass="label"/>
<h:inputText id="title" value="#{sliderAction.newSlide.title}" name="#{sliderAction.slide.title}" required="true"/>
<p></p><h:message for="title" errorClass="error"/>
<h:outputText value="#{msgs.description}" styleClass="label"/>
<h:inputTextarea id="description" value="#{sliderAction.newSlide.description}" name="#{sliderAction.slide.description}" required="true"/>
<p></p><h:message for="description" errorClass="error"/>
<p></p>
<h:commandButton value="#{msgs.save}" action="#{sliderAction.saveChanges}" styleClass="button"/>
<h:message for="edit-slide-form"/>
</h:panelGrid>
</h:form>
When I submit this form I can get only last value (e.g id="description").
SliderAction.class
#Named
#SessionScoped
public class SliderAction implements Serializable {
private static final long serialVersionUID = 1L;
private EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("iaau");
private EntityManager entityManager;
private int id;
private Integer coordinates[] = new Integer[4];
private Image image = new Image();
private Slide slide = new Slide();
private Slide newSlide = new Slide();
private List<Slide> slides = new ArrayList<Slide>();
private UploadedFile uploadedFile;
private boolean uploaded;
public String uploadSlideImage() {
ImageAction imageAction = new ImageAction();
if( uploaded ) {
imageAction.delete( image.getUniqueName(), image.getThumb() );
for( int i = 0; i < coordinates.length; i++ ) {
coordinates[i] = null;
}
}
imageAction.upload(uploadedFile, false);
setImage( imageAction.getImage() );
uploaded = true;
return null;
}
public String cropAndSave(){
ImageAction imageAction = new ImageAction();
imageAction.cropSlide( image, coordinates, 928, 318 );
newSlide = new Slide();
newSlide.setImage( image );
startTransaction();
entityManager.persist(image);
entityManager.persist(newSlide);
endTransaction();
closeTransaction();
uploaded = false;
setId(newSlide.getId());
return "edit";
}
public String saveChanges() {
startTransaction();
entityManager.merge(newSlide);
endTransaction();
closeTransaction();
return "list";
}
public void deleteSlide() {
startTransaction();
slide = (Slide)entityManager.find(Slide.class, id);
entityManager.remove(slide);
endTransaction();
closeTransaction();
ImageAction imageAtion = new ImageAction();
imageAtion.delete(slide.getImage().getUniqueName(), slide.getImage().getThumb());
}
public Slide getSlide() {
startTransaction();
slide = entityManager.find(Slide.class, id);
endTransaction();
closeTransaction();
return slide;
}
public void setSlide(Slide slide) {
this.slide = slide;
}
public Slide getNewSlide() {
startTransaction();
newSlide = entityManager.find(Slide.class, id);
endTransaction();
closeTransaction();
return newSlide;
}
public void setNewSlide(Slide newSlide) {
this.newSlide = newSlide;
}
#SuppressWarnings("unchecked")
public List<Slide> getSlides() {
startTransaction();
slides = entityManager.createQuery("from Slide").getResultList();
endTransaction();
closeTransaction();
return slides;
}
/*Declaration of EntityManager and other getter and setter methods*/
}
Slide.class
#Entity
public class Slide extends AbstractEntity<Integer>{
private static final long serialVersionUID = 1L;
private String title;
private String description;
private Image image;
private String link;
private Page page;
private boolean showOnFeed;
private boolean approved;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#OneToOne
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
#OneToOne
public Page getPage() {
return page;
}
public void setPage(Page page) {
this.page = page;
}
#Column(name="show_on_feed")
public boolean isShowOnFeed() {
return showOnFeed;
}
public void setShowOnFeed(boolean showOnFeed) {
this.showOnFeed = showOnFeed;
}
public boolean isApproved() {
return approved;
}
public void setApproved(boolean approved) {
this.approved = approved;
}
}
Also I have notices this warning message:
[org.hibernate.ejb.internal.EntityManagerFactoryRegistry] (http-localhost-127.0.0.1-8080-1) HHH000436: Entity manager factory name (iaau) is already registered. If entity manager will be clustered or passivated, specify a unique value for property 'hibernate.ejb.entitymanager_factory_name'
can this cause such problems

Binding data from ObservableCollection<User> to xaml page

i am trying to bind the data from ObservableCollection to xaml page
pls let me know hoe to achive this
Xaml page
<UserControl.Resources>
<DataTemplate x:Key="User" >
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource User}}">
<data:DataGrid AutoGenerateColumns="True"
HeadersVisibility="All"
ItemsSource="{Binding }"
RowBackground="Cornsilk"
AlternatingRowBackground="BlanchedAlmond"
ColumnWidth="85" RowHeight="30"
IsReadOnly="True" CanUserResizeColumns="False">
</data:DataGrid>
</Grid>
.cs page
ObservableCollection<User> users;
public SLObservableCollectionSample()
{
InitializeComponent();
users = getUserCollection();
}
ObservableCollection<User> getUserCollection()
{
ObservableCollection<User> rVal = new ObservableCollection<User>();
rVal.Add(new User { Name = "Tom", Score = 2 });
rVal.Add(new User { Name = "Sam", Score = 3 });
rVal.Add(new User { Name = "John", Score = 4 });
rVal.Add(new User { Name = "Dave", Score = 5 });
rVal.Add(new User { Name = "Sue", Score = 1 });
return rVal;
}
#region User INotifyPropertyChanged
public class User : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
onPropertyChanged(this, "Name");
}
}
}
private long score;
public long Score
{
get { return score; }
set
{
if (score != value)
{
score = value;
onPropertyChanged(this, "Score");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#endregion
Thanks
Prince

WPF - Binding to CheckBox not working in a HierarchicalDataTemplate

In a WPF treeview I am trying to automatically check the children if the parent node is being checked. I am using a view model for that and a bindable object for the nodes, however all my attempts failed. Here is the code (C# + XAML). Any ideas would be greatly appreciated
<Window x:Class="TestCheckBoxBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestCheckBoxBinding"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:TestCategory}" ItemsSource="{Binding Tests, Mode=OneTime}">
<Label Content="{Binding Name}"></Label>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Test}" ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsChecked, Mode=TwoWay}"></CheckBox>
<Label Content="{Binding Name}"></Label>
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Categories, Mode=OneTime}"></TreeView>
</Grid>
public class TestsViewModel
{
public static void PopulateList(TestsViewModel vm)
{
TestCategory cat1 = new TestCategory() { Id = 1, Name = "First category" };
Test t1 = new Test() { Name = "Test1" };
Test t2 = new Test() { Name = "Test2" };
Test t3 = new Test() { Name = "Test3" };
t1.AddChild(t2);
t1.AddChild(t3);
Test t4 = new Test() { Name = "Test4" };
cat1.AddTest(t1);
cat1.AddTest(t4);
vm.AddTestCategory(cat1);
TestCategory cat2 = new TestCategory() { Id = 2, Name = "Second category" };
Test t5 = new Test() { Name = "Test1" };
Test t6 = new Test() { Name = "Test2" };
Test t7 = new Test() { Name = "Test3" };
t6.AddChild(t7);
Test t8 = new Test() { Name = "Test4" };
cat2.AddTest(t5);
cat2.AddTest(t6);
cat2.AddTest(t8);
vm.AddTestCategory(cat2);
}
private readonly IEnumerable<TestCategory> categories = new List<TestCategory>();
public IEnumerable<TestCategory> Categories { get { return categories; } }
public void AddTestCategory(TestCategory testCategory)
{
((IList<TestCategory>)categories).Add(testCategory);
}
}
public class TestCategory
{
public int Id { get; set; }
public string Name { get; set; }
private readonly IEnumerable<Test> tests = new List<Test>();
public IEnumerable<Test> Tests { get { return tests; } }
public void AddTest(Test t)
{
((IList<Test>)tests).Add(t);
}
}
public class Test : INotifyPropertyChanged
{
private string name;
public string Name
{
set
{
if (name != value)
{
name = value;
this.OnPropertyChanged("Name");
}
}
get { return name; }
}
public bool? isChecked = false;
public bool? IsChecked
{
get { return isChecked; }
set
{
if (isChecked != value)
{
isChecked = value;
if (children.Count() > 0)
{
foreach (var test in children)
{
test.isChecked = value;
test.Name += ".";
}
}
this.OnPropertyChanged("IsChecked");
}
}
}
public void AddChild(Test test)
{
((IList<Test>)children).Add(test);
}
private readonly IEnumerable<Test> children = new List<Test>();
public IEnumerable<Test> Children
{
get { return children; }
}
#region INotifyPropertyChanged Members
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Oups, child checkboxes are not being checked because I wasn't setting their IsChecked property. I was setting the isChecked field, which bypasses the property setter and prevents PropertyChanged from being raised.

Resources