Merge branch '5.0.0' into main
This commit is contained in:
Коммит
6d9a4b0d80
|
@ -54,8 +54,10 @@ namespace Xamarin.Forms.Platform
|
|||
[RenderWith(typeof(ImageButtonRenderer))]
|
||||
internal class _ImageButtonRenderer { }
|
||||
|
||||
#if !__IOS__
|
||||
[RenderWith(typeof(RadioButtonRenderer))]
|
||||
internal class _RadioButtonRenderer { }
|
||||
#endif
|
||||
|
||||
[RenderWith (typeof (TableViewRenderer))]
|
||||
internal class _TableViewRenderer { }
|
||||
|
|
|
@ -411,4 +411,9 @@
|
|||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties XamarinHotReloadUnhandledDeviceExceptionXamarinFormsControlGalleryAndroidHideInfoBar="True" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms.CustomAttributes;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
|
@ -36,7 +37,10 @@ namespace Xamarin.Forms.Controls.Issues
|
|||
_webView = new WebView()
|
||||
{
|
||||
Source = "https://dotnet.microsoft.com/apps/xamarin",
|
||||
Cookies = new System.Net.CookieContainer()
|
||||
Cookies = new System.Net.CookieContainer(),
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
VerticalOptions = LayoutOptions.Fill,
|
||||
HeightRequest = 600
|
||||
};
|
||||
|
||||
_webView.Navigating += (_, __) =>
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries;
|
||||
using Xamarin.Forms.Controls.Issues;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.PlatformConfiguration;
|
||||
|
|
|
@ -21,6 +21,8 @@ namespace Xamarin.Forms.Controls
|
|||
|
||||
internal CoreGalleryPage()
|
||||
{
|
||||
Initialize();
|
||||
|
||||
Layout = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(20)
|
||||
|
@ -55,6 +57,8 @@ namespace Xamarin.Forms.Controls
|
|||
}
|
||||
}
|
||||
|
||||
protected virtual void Initialize() { }
|
||||
|
||||
protected virtual void InitializeElement(T element) { }
|
||||
|
||||
protected virtual void Build(StackLayout stackLayout)
|
||||
|
|
|
@ -9,8 +9,13 @@ namespace Xamarin.Forms.Controls
|
|||
protected override bool SupportsTapGestureRecognizer => true;
|
||||
protected override void InitializeElement(RadioButton element)
|
||||
{
|
||||
element.Content = "RadioButton";
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Device.SetFlags(new List<string> { ExperimentalFlags.RadioButtonExperimental });
|
||||
element.Text = "RadioButton";
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
@ -23,12 +28,12 @@ namespace Xamarin.Forms.Controls
|
|||
{
|
||||
base.Build(stackLayout);
|
||||
|
||||
IsEnabledStateViewContainer.View.Clicked += (sender, args) => IsEnabledStateViewContainer.TitleLabel.Text += " (Tapped)";
|
||||
IsEnabledStateViewContainer.View.CheckedChanged += (sender, args) => IsEnabledStateViewContainer.TitleLabel.Text += " (Checked Changed)";
|
||||
|
||||
var borderButtonContainer = new ViewContainer<RadioButton>(Test.Button.BorderColor,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "BorderColor",
|
||||
Content = "BorderColor",
|
||||
BackgroundColor = Color.Transparent,
|
||||
BorderColor = Color.Red,
|
||||
BorderWidth = 1,
|
||||
|
@ -38,7 +43,7 @@ namespace Xamarin.Forms.Controls
|
|||
var borderRadiusContainer = new ViewContainer<RadioButton>(Test.Button.BorderRadius,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "BorderRadius",
|
||||
Content = "BorderRadius",
|
||||
BackgroundColor = Color.Transparent,
|
||||
BorderColor = Color.Red,
|
||||
BorderWidth = 1,
|
||||
|
@ -48,56 +53,33 @@ namespace Xamarin.Forms.Controls
|
|||
var borderWidthContainer = new ViewContainer<RadioButton>(Test.Button.BorderWidth,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "BorderWidth",
|
||||
Content = "BorderWidth",
|
||||
BackgroundColor = Color.Transparent,
|
||||
BorderColor = Color.Red,
|
||||
BorderWidth = 15,
|
||||
}
|
||||
);
|
||||
|
||||
var clickedContainer = new EventViewContainer<RadioButton>(Test.Button.Clicked,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "Clicked"
|
||||
}
|
||||
);
|
||||
clickedContainer.View.Clicked += (sender, args) => clickedContainer.EventFired();
|
||||
|
||||
var pressedContainer = new EventViewContainer<RadioButton>(Test.Button.Pressed,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "Pressed"
|
||||
}
|
||||
);
|
||||
pressedContainer.View.Pressed += (sender, args) => pressedContainer.EventFired();
|
||||
|
||||
var commandContainer = new ViewContainer<RadioButton>(Test.Button.Command,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "Command",
|
||||
Command = new Command(() => DisplayActionSheet("Hello Command", "Cancel", "Destroy"))
|
||||
}
|
||||
);
|
||||
|
||||
var fontContainer = new ViewContainer<RadioButton>(Test.Button.Font,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "Font",
|
||||
Font = Font.SystemFontOfSize(NamedSize.Large, FontAttributes.Bold)
|
||||
Content = "Font",
|
||||
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(RadioButton)),
|
||||
FontAttributes = FontAttributes.Bold
|
||||
}
|
||||
);
|
||||
|
||||
var textContainer = new ViewContainer<RadioButton>(Test.Button.Text,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "Text"
|
||||
Content = "Text"
|
||||
}
|
||||
);
|
||||
|
||||
var textColorContainer = new ViewContainer<RadioButton>(Test.Button.TextColor,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "TextColor",
|
||||
Content = "TextColor",
|
||||
TextColor = Color.Pink
|
||||
}
|
||||
);
|
||||
|
@ -105,7 +87,7 @@ namespace Xamarin.Forms.Controls
|
|||
var paddingContainer = new ViewContainer<RadioButton>(Test.Button.Padding,
|
||||
new RadioButton
|
||||
{
|
||||
Text = "Padding",
|
||||
Content = "Padding",
|
||||
BackgroundColor = Color.Red,
|
||||
Padding = new Thickness(20, 30, 60, 15)
|
||||
}
|
||||
|
@ -113,36 +95,14 @@ namespace Xamarin.Forms.Controls
|
|||
|
||||
var isCheckedContainer = new ValueViewContainer<RadioButton>(Test.RadioButton.IsChecked, new RadioButton() { IsChecked = true, HorizontalOptions = LayoutOptions.Start }, "IsChecked", value => value.ToString());
|
||||
|
||||
//var checkedVisualState = new VisualState { Name = "IsChecked" };
|
||||
//checkedVisualState.Setters.Add(new Setter { Property = RadioButton.ButtonSourceProperty, Value = "rb_checked" });
|
||||
|
||||
//var group = new VisualStateGroup();
|
||||
//group.States.Add(checkedVisualState);
|
||||
|
||||
//var normalVisualState = new VisualState{ Name = "Normal" };
|
||||
//normalVisualState.Setters.Add(new Setter { Property = RadioButton.ButtonSourceProperty, Value = "rb_unchecked" });
|
||||
//group.States.Add(normalVisualState);
|
||||
|
||||
//var groupList = new VisualStateGroupList();
|
||||
//groupList.Add(group);
|
||||
|
||||
//var rbStateManaged = new RadioButton() { HorizontalOptions = LayoutOptions.Start };
|
||||
//VisualStateManager.SetVisualStateGroups(rbStateManaged, groupList);
|
||||
|
||||
//var stateManagedContainer = new ValueViewContainer<RadioButton>(Test.RadioButton.ButtonSource, rbStateManaged, "IsChecked", value => value.ToString());
|
||||
|
||||
Add(borderButtonContainer);
|
||||
Add(borderRadiusContainer);
|
||||
Add(borderWidthContainer);
|
||||
Add(clickedContainer);
|
||||
Add(pressedContainer);
|
||||
Add(commandContainer);
|
||||
Add(fontContainer);
|
||||
Add(textContainer);
|
||||
Add(textColorContainer);
|
||||
Add(paddingContainer);
|
||||
Add(isCheckedContainer);
|
||||
//Add(stateManagedContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?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:d="http://xamarin.com/schemas/2014/forms/design"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries.ContentProperties">
|
||||
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Label Text="Propagate standard Text properties to Content where applicable:"/>
|
||||
<Label FontSize="Small" Text="TextColor: Red, CharacterSpacing: 1.5, TextTransform: Lowercase, FontAttributes: Italic, FontSize: 14, FontFamily: BaskerVille"/>
|
||||
<RadioButton Content="Option A" GroupName="test"
|
||||
TextColor="Red"
|
||||
CharacterSpacing="1.5"
|
||||
TextTransform="Lowercase"
|
||||
FontAttributes="Italic"
|
||||
FontSize="14"
|
||||
FontFamily="Baskerville"
|
||||
/>
|
||||
<Label FontSize="Small" Text="TextColor: Blue, CharacterSpacing: 1, TextTransform: Uppercase, FontAttributes: Bold, FontSize: 18, FontFamily: Arial"/>
|
||||
<RadioButton Content="Option B" GroupName="test"
|
||||
TextColor="Blue"
|
||||
TextTransform="Uppercase"
|
||||
FontAttributes="Bold"
|
||||
FontSize="18"
|
||||
FontFamily="Arial"
|
||||
/>
|
||||
|
||||
<Label FontSize="Small" Text="The RadioButton below has its content set to Button (which makes little sense, but this is just an example). Anyway, the Text and Font properties are applied to the Button."/>
|
||||
|
||||
<RadioButton GroupName="test"
|
||||
TextColor="Green"
|
||||
TextTransform="Uppercase"
|
||||
FontAttributes="Bold"
|
||||
FontSize="12"
|
||||
FontFamily="Arial">
|
||||
|
||||
<RadioButton.Content>
|
||||
<Button Text="It's a button inside a button."></Button>
|
||||
</RadioButton.Content>
|
||||
|
||||
</RadioButton>
|
||||
|
||||
<Label FontSize="Small" Text="A Content View which already has these properties set/bound should ignore the RadioButton properties."/>
|
||||
|
||||
<RadioButton GroupName="test"
|
||||
TextColor="Green"
|
||||
TextTransform="Uppercase"
|
||||
FontAttributes="Bold"
|
||||
FontSize="12"
|
||||
FontFamily="Arial">
|
||||
|
||||
<RadioButton.Content>
|
||||
|
||||
<Label x:Name="MainLabel" Text="Properties already set."
|
||||
TextColor="Purple"
|
||||
TextTransform="Lowercase"
|
||||
FontSize="Micro"
|
||||
FontFamily="Baskerville"
|
||||
FontAttributes="Italic" />
|
||||
|
||||
|
||||
</RadioButton.Content>
|
||||
|
||||
</RadioButton>
|
||||
|
||||
<RadioButton GroupName="test"
|
||||
TextColor="Green"
|
||||
TextTransform="Uppercase"
|
||||
FontAttributes="Bold"
|
||||
FontSize="12"
|
||||
FontFamily="Arial">
|
||||
|
||||
<RadioButton.Content>
|
||||
<Label Text="Properties already bound"
|
||||
BindingContext="{x:Reference MainLabel}"
|
||||
TextColor="{Binding TextColor}"
|
||||
TextTransform="{Binding TextTransform}"
|
||||
FontSize="{Binding FontSize}"
|
||||
FontFamily="{Binding FontFamily}"
|
||||
FontAttributes="{Binding FontAttributes}" />
|
||||
</RadioButton.Content>
|
||||
|
||||
</RadioButton>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,13 @@
|
|||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class ContentProperties : ContentPage
|
||||
{
|
||||
public ContentProperties()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
<?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:d="http://xamarin.com/schemas/2014/forms/design"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries.RadioButtonContentGallery">
|
||||
<ContentPage.Content>
|
||||
<ScrollView>
|
||||
<StackLayout>
|
||||
|
||||
<Label Text="We can set 'Content' on a RadioButton to a string:"/>
|
||||
<RadioButton Content="Option A" />
|
||||
|
||||
<Label Text="If 'Content' is just a String, it will be translated to Text (always supported):"/>
|
||||
<RadioButton Content="Option C" />
|
||||
|
||||
<Label Text="If 'Content' is a View, it will be displayed where supported (e.g., when using ControlTemplates):"/>
|
||||
|
||||
<Frame>
|
||||
<Frame.Content>
|
||||
<OnPlatform x:TypeArguments="View">
|
||||
<On Platform="iOS,UWP">
|
||||
|
||||
<RadioButton>
|
||||
<RadioButton.Content>
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Image Source="coffee.png"></Image>
|
||||
<Label LineBreakMode="WordWrap" Text="This platform can handle Views as Content"/>
|
||||
</StackLayout>
|
||||
</RadioButton.Content>
|
||||
</RadioButton>
|
||||
|
||||
</On>
|
||||
<On Platform="Android,Tizen,WPF">
|
||||
<RadioButton Content="Can't use View for Content on this platform, so just plain old text"></RadioButton>
|
||||
</On>
|
||||
</OnPlatform>
|
||||
</Frame.Content>
|
||||
</Frame>
|
||||
|
||||
<Label Text="This will display the Content (coffee cup) where it can, and fall back to the string representation where it cannot:"></Label>
|
||||
<RadioButton>
|
||||
<RadioButton.Content>
|
||||
<Image Source="coffee.png"/>
|
||||
</RadioButton.Content>
|
||||
</RadioButton>
|
||||
|
||||
<Label Text="If we select a ControlTemplate, we can use a View as Content on any platform. In this case, we're using the default template:"/>
|
||||
<RadioButton ControlTemplate="{x:Static RadioButton.DefaultTemplate}">
|
||||
<RadioButton.Content>
|
||||
<Image Source="coffee.png"/>
|
||||
</RadioButton.Content>
|
||||
</RadioButton>
|
||||
|
||||
<Label Text="Also, here's a group with a weird-looking custom template:"/>
|
||||
<RadioButton GroupName="weird">
|
||||
<RadioButton.ControlTemplate>
|
||||
|
||||
<ControlTemplate>
|
||||
|
||||
<StackLayout Orientation="Horizontal">
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroupList>
|
||||
|
||||
<VisualStateGroup x:Name="CheckedStates">
|
||||
<VisualState x:Name="Checked">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Opacity" TargetName="CheckedIndicator" Value="1"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Unchecked">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Opacity" TargetName="CheckedIndicator" Value="0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
|
||||
|
||||
<Line x:Name="Button" InputTransparent="True" Stroke="Black" StrokeThickness="2" X1="0" Y1="0" X2="50" Y2="50"></Line>
|
||||
<Line x:Name="CheckedIndicator" InputTransparent="True" TranslationX="-50" Opacity="0" Stroke="Red" StrokeThickness="2" X1="50" Y1="0" X2="0" Y2="50"></Line>
|
||||
<ContentPresenter></ContentPresenter>
|
||||
</StackLayout>
|
||||
</ControlTemplate>
|
||||
</RadioButton.ControlTemplate>
|
||||
<RadioButton.Content>
|
||||
<Image Source="coffee.png"></Image>
|
||||
</RadioButton.Content>
|
||||
</RadioButton>
|
||||
|
||||
<RadioButton GroupName="weird">
|
||||
<RadioButton.ControlTemplate>
|
||||
<ControlTemplate>
|
||||
|
||||
<StackLayout Orientation="Horizontal" x:Name="Root">
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroupList>
|
||||
|
||||
<VisualStateGroup x:Name="CheckedStates">
|
||||
<VisualState x:Name="Checked">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Opacity" TargetName="CheckedIndicator" Value="1"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Unchecked">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="Opacity" TargetName="CheckedIndicator" Value="0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
|
||||
|
||||
<Line x:Name="Button" InputTransparent="True" Stroke="Black" StrokeThickness="2" X1="0" Y1="0" X2="50" Y2="50"></Line>
|
||||
<Line x:Name="CheckedIndicator" InputTransparent="True" TranslationX="-50" Opacity="0" Stroke="Red" StrokeThickness="2" X1="50" Y1="0" X2="0" Y2="50"></Line>
|
||||
<ContentPresenter></ContentPresenter>
|
||||
</StackLayout>
|
||||
</ControlTemplate>
|
||||
</RadioButton.ControlTemplate>
|
||||
<RadioButton.Content>
|
||||
<Image Source="coffee.png"></Image>
|
||||
</RadioButton.Content>
|
||||
</RadioButton>
|
||||
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class RadioButtonContentGallery : ContentPage
|
||||
{
|
||||
public RadioButtonContentGallery()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,19 @@
|
|||
descriptionLabel,
|
||||
button,
|
||||
GalleryBuilder.NavButton("RadioButton Group Gallery", () =>
|
||||
new RadioButtonGroupGalleryPage(), Navigation)
|
||||
new RadioButtonGroupGalleryPage(), Navigation),
|
||||
GalleryBuilder.NavButton("RadioButton Group (Attached Property)", () =>
|
||||
new RadioButtonGroupGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("RadioButton Group (Attached Property, Binding)", () =>
|
||||
new RadioButtonGroupBindingGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("RadioButton Group (Across Multiple Containers)", () =>
|
||||
new ScatteredRadioButtonGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("RadioButton Content", () =>
|
||||
new RadioButtonContentGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("RadioButton Content Properties", () =>
|
||||
new ContentProperties(), Navigation),
|
||||
GalleryBuilder.NavButton("RadioButton Template from Style", () =>
|
||||
new TemplateFromStyle(), Navigation),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?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:d="http://xamarin.com/schemas/2014/forms/design"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries.RadioButtonGroupBindingGallery">
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
|
||||
<Grid RadioButtonGroup.GroupName="{Binding GroupName}"
|
||||
RadioButtonGroup.SelectedValue="{Binding Selection}">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition Height="50"></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.ColumnSpan="2" Text="The RadioButtons in this Grid have a GroupName and Selection bound to a ViewModel."></Label>
|
||||
|
||||
<Label Text="{Binding GroupName, StringFormat='The GroupName is {0}'}" Grid.Row="1" />
|
||||
<Label Text="{Binding Selection, StringFormat='The Selection is {0}'}" Grid.Row="1" Grid.Column="1" />
|
||||
|
||||
<RadioButton Content="Option A" Value="A" Grid.Row="2"></RadioButton>
|
||||
<RadioButton Content="Option B" Value="B" Grid.Row="2" Grid.Column="1"></RadioButton>
|
||||
<RadioButton Content="Option C" Value="C" Grid.Row="3"></RadioButton>
|
||||
<RadioButton Content="Option D" Value="D" Grid.Row="3" Grid.Column="1"></RadioButton>
|
||||
|
||||
<Button Margin="5" Grid.ColumnSpan="2" Grid.Row="4" Text="Set selection in view model to 'B'" Clicked="Button_Clicked"></Button>
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,56 @@
|
|||
using System.ComponentModel;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class RadioButtonGroupBindingGallery : ContentPage
|
||||
{
|
||||
RadioButtonGroupBindingModel _viewModel;
|
||||
|
||||
public RadioButtonGroupBindingGallery()
|
||||
{
|
||||
InitializeComponent();
|
||||
_viewModel = new RadioButtonGroupBindingModel() { GroupName = "group1" };
|
||||
BindingContext = _viewModel;
|
||||
}
|
||||
|
||||
private void Button_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
_viewModel.Selection = "B";
|
||||
}
|
||||
}
|
||||
|
||||
public class RadioButtonGroupBindingModel : INotifyPropertyChanged
|
||||
{
|
||||
private string _groupName;
|
||||
private object _selection;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public string GroupName
|
||||
{
|
||||
get => _groupName;
|
||||
set
|
||||
{
|
||||
_groupName = value;
|
||||
OnPropertyChanged(nameof(GroupName));
|
||||
}
|
||||
}
|
||||
|
||||
public object Selection
|
||||
{
|
||||
get => _selection;
|
||||
set
|
||||
{
|
||||
_selection = value;
|
||||
OnPropertyChanged(nameof(Selection));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?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:d="http://xamarin.com/schemas/2014/forms/design"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries.RadioButtonGroupGallery">
|
||||
<ContentPage.Content>
|
||||
<StackLayout RadioButtonGroup.GroupName="foo">
|
||||
<Label Text="All of the RadioButtons in this StackLayout will automatically be given the GroupName of 'foo'"/>
|
||||
<RadioButton Content="Option A" />
|
||||
<RadioButton Content="Option B" />
|
||||
<RadioButton Content="Option C" />
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Text="This RadioButton is inside a Grid"></Label>
|
||||
<RadioButton Content="Option D" Grid.Column="1" />
|
||||
</Grid>
|
||||
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,13 @@
|
|||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class RadioButtonGroupGallery : ContentPage
|
||||
{
|
||||
public RadioButtonGroupGallery()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,12 +6,12 @@
|
|||
<ResourceDictionary>
|
||||
<DataTemplate x:Key="NoGroupNameLVItemTemplate">
|
||||
<ViewCell>
|
||||
<RadioButton Text="RadioButton, Group=null"/>
|
||||
<RadioButton Content="RadioButton, Group=null"/>
|
||||
</ViewCell>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="GroupNameLVItemTemplate">
|
||||
<ViewCell>
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'"/>
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'"/>
|
||||
</ViewCell>
|
||||
</DataTemplate>
|
||||
<ControlTemplate x:Key="NoGroupNameControlTemplate">
|
||||
|
@ -29,31 +29,31 @@
|
|||
Margin="0, 0, 0, 10"/>
|
||||
<Label Text="StackLayout" />
|
||||
<StackLayout>
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Text="RadioButton, Group=null"/>
|
||||
<RadioButton Text="RadioButton, Group=null"/>
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null"/>
|
||||
<RadioButton Content="RadioButton, Group=null"/>
|
||||
</StackLayout>
|
||||
<Label Text="StackLayout" Margin="0, 10"/>
|
||||
<StackLayout>
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
</StackLayout>
|
||||
<Label Text="ScrollView" />
|
||||
<ScrollView>
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
</ScrollView>
|
||||
<Label Text="ContentView" />
|
||||
<ContentView>
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
</ContentView>
|
||||
<Label Text="Frame" />
|
||||
<Frame>
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
</Frame>
|
||||
<Label Text="ContentView with ControlTemplate" />
|
||||
<ContentView ControlTemplate="{StaticResource NoGroupNameControlTemplate}">
|
||||
<RadioButton Text="RadioButton, Group=null" />
|
||||
<RadioButton Content="RadioButton, Group=null" />
|
||||
</ContentView>
|
||||
<Label Text="ListView with ItemTemplate" />
|
||||
<ListView ItemTemplate="{StaticResource NoGroupNameLVItemTemplate}"
|
||||
|
@ -78,31 +78,31 @@
|
|||
Margin="0, 0, 0, 10"/>
|
||||
<Label Text="StackLayout" />
|
||||
<StackLayout>
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'"/>
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'"/>
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'"/>
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'"/>
|
||||
</StackLayout>
|
||||
<Label Text="StackLayout" Margin="0, 10"/>
|
||||
<StackLayout>
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
</StackLayout>
|
||||
<Label Text="ScrollView" />
|
||||
<ScrollView>
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
</ScrollView>
|
||||
<Label Text="ContentView" />
|
||||
<ContentView>
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
</ContentView>
|
||||
<Label Text="Frame" />
|
||||
<Frame>
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
</Frame>
|
||||
<Label Text="ContentView with ControlTemplate" />
|
||||
<ContentView ControlTemplate="{StaticResource GroupNameControlTemplate}">
|
||||
<RadioButton GroupName="A" Text="RadioButton, Group='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, Group='A'" />
|
||||
</ContentView>
|
||||
<Label Text="ListView with ItemTemplate" />
|
||||
<ListView ItemTemplate="{StaticResource GroupNameLVItemTemplate}"
|
||||
|
@ -127,23 +127,23 @@
|
|||
Margin="0, 0, 0, 10"/>
|
||||
<Label Text="StackLayout" />
|
||||
<StackLayout>
|
||||
<RadioButton GroupName="A" Text="RadioButton, GroupName='A'" />
|
||||
<RadioButton GroupName="A" Text="RadioButton, GroupName='A'" />
|
||||
<RadioButton Text="RadioButton, GroupName=null" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, GroupName='A'" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, GroupName='A'" />
|
||||
<RadioButton Content="RadioButton, GroupName=null" />
|
||||
</StackLayout>
|
||||
<StackLayout Margin="0, 10">
|
||||
<RadioButton GroupName="A" Text="RadioButton, GroupName='A'" />
|
||||
<RadioButton GroupName="B" Text="RadioButton, GroupName='B'" />
|
||||
<RadioButton GroupName="B" Text="RadioButton, GroupName='B'" />
|
||||
<RadioButton Text="RadioButton, GroupName=null" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, GroupName='A'" />
|
||||
<RadioButton GroupName="B" Content="RadioButton, GroupName='B'" />
|
||||
<RadioButton GroupName="B" Content="RadioButton, GroupName='B'" />
|
||||
<RadioButton Content="RadioButton, GroupName=null" />
|
||||
</StackLayout>
|
||||
<StackLayout>
|
||||
<RadioButton GroupName="A" Text="RadioButton, GroupName='A'" />
|
||||
<RadioButton GroupName="B" Text="RadioButton, GroupName='B'" />
|
||||
<RadioButton GroupName="C" Text="RadioButton, GroupName='C'" />
|
||||
<RadioButton GroupName="C" Text="RadioButton, GroupName='C'" />
|
||||
<RadioButton Text="RadioButton, GroupName=null" />
|
||||
<RadioButton Text="RadioButton, GroupName=null" />
|
||||
<RadioButton GroupName="A" Content="RadioButton, GroupName='A'" />
|
||||
<RadioButton GroupName="B" Content="RadioButton, GroupName='B'" />
|
||||
<RadioButton GroupName="C" Content="RadioButton, GroupName='C'" />
|
||||
<RadioButton GroupName="C" Content="RadioButton, GroupName='C'" />
|
||||
<RadioButton Content="RadioButton, GroupName=null" />
|
||||
<RadioButton Content="RadioButton, GroupName=null" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?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:d="http://xamarin.com/schemas/2014/forms/design"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries.ScatteredRadioButtonGallery">
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Label Text="RadioButtons don't have to be in the same container to be grouped." />
|
||||
|
||||
<Label Text="Here, we have a few in a nested StackLayout:"></Label>
|
||||
<StackLayout Orientation="Horizontal" BackgroundColor="AliceBlue" RadioButtonGroup.GroupName="foo">
|
||||
<RadioButton Content="A"></RadioButton>
|
||||
<RadioButton Content="B"></RadioButton>
|
||||
<RadioButton Content="C"></RadioButton>
|
||||
</StackLayout>
|
||||
|
||||
<Label Text="And another outside that StackLayout with the same GroupName:"></Label>
|
||||
|
||||
<RadioButton Content="D (None of the above)" GroupName="foo"></RadioButton>
|
||||
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class ScatteredRadioButtonGallery : ContentPage
|
||||
{
|
||||
public ScatteredRadioButtonGallery()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?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="Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries.TemplateFromStyle">
|
||||
|
||||
<ContentPage.Resources>
|
||||
|
||||
<ControlTemplate x:Key="CalendarRadioTemplate">
|
||||
<Frame BorderColor="#F3F2F1" BackgroundColor="#F3F2F1" HasShadow="False"
|
||||
HeightRequest="100" WidthRequest="100" HorizontalOptions="Start" VerticalOptions="Start" Padding="0">
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CheckedStates">
|
||||
|
||||
<VisualState x:Name="Checked">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BorderColor" Value="#FF3300"/>
|
||||
<Setter TargetName="Check" Property="Opacity" Value="1"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Unchecked">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor" Value="#f3f2f1"/>
|
||||
<Setter Property="BorderColor" Value="#f3f2f1"/>
|
||||
<Setter TargetName="Check" Property="Opacity" Value="0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
|
||||
<Grid Margin="4" WidthRequest="100">
|
||||
<Grid WidthRequest="18" HeightRequest="18" HorizontalOptions="End" VerticalOptions="Start">
|
||||
<Ellipse Stroke="Blue" WidthRequest="16" HeightRequest="16" StrokeThickness="0.5" VerticalOptions="Center" HorizontalOptions="Center" Fill="White" />
|
||||
<Ellipse x:Name="Check" WidthRequest="8" HeightRequest="8" Fill="Blue" VerticalOptions="Center" HorizontalOptions="Center" />
|
||||
</Grid>
|
||||
<ContentPresenter></ContentPresenter>
|
||||
</Grid>
|
||||
</Frame>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="RadioButton">
|
||||
<Setter Property="ControlTemplate" Value="{StaticResource CalendarRadioTemplate}"/>
|
||||
</Style>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<RadioButton GroupName="A" Content="A"/>
|
||||
<RadioButton GroupName="A" Content="B"/>
|
||||
<RadioButton GroupName="A" Content="C"/>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Forms.Controls.GalleryPages.RadioButtonGalleries
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class TemplateFromStyle : ContentPage
|
||||
{
|
||||
public TemplateFromStyle()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<?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="Xamarin.Forms.Controls.GalleryPages.ShapesGalleries.ClipCornerRadiusGallery"
|
||||
Title="Clip CornerRadius Gallery">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<Style x:Key="ImageContainerStyle" TargetType="Grid">
|
||||
<Setter Property="BackgroundColor" Value="LightGray" />
|
||||
<Setter Property="HorizontalOptions" Value="Start" />
|
||||
<Setter Property="HeightRequest" Value="200" />
|
||||
<Setter Property="WidthRequest" Value="200" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="FontSize" Value="Small" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Image">
|
||||
<Setter Property="Aspect" Value="AspectFill" />
|
||||
<Setter Property="HorizontalOptions" Value="Start" />
|
||||
<Setter Property="HeightRequest" Value="200" />
|
||||
<Setter Property="WidthRequest" Value="200" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Slider">
|
||||
<Setter Property="Minimum" Value="0" />
|
||||
<Setter Property="MinimumTrackColor" Value="LightGray" />
|
||||
<Setter Property="Maximum" Value="60" />
|
||||
<Setter Property="MaximumTrackColor" Value="Gray" />
|
||||
<Setter Property="HorizontalOptions" Value="FillAndExpand" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
<ContentPage.Content>
|
||||
<StackLayout
|
||||
Padding="12">
|
||||
<Label
|
||||
Text="Clipped Image using RoundRectangleGeometry"/>
|
||||
<Grid
|
||||
Style="{StaticResource ImageContainerStyle}">
|
||||
<Image
|
||||
Source="crimson.jpg">
|
||||
<Image.Clip>
|
||||
<RoundRectangleGeometry
|
||||
x:Name="RoundRectangleGeometry"
|
||||
Rect="0, 0, 150, 150"/>
|
||||
</Image.Clip>
|
||||
</Image>
|
||||
</Grid>
|
||||
<!-- TOP LEFT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Top Left Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="TopLeftCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
<!-- TOP RIGHT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Top Right Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="TopRightCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
<!-- BOTTOM LEFT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Bottom Left Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="BottomLeftCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
<!-- BOTTOM RIGHT CORNER -->
|
||||
<Label
|
||||
FontSize="Medium"
|
||||
Text="Bottom Right Corner"
|
||||
VerticalTextAlignment="Center" />
|
||||
<Slider
|
||||
x:Name="BottomRightCorner"
|
||||
ValueChanged="OnCornerChanged"/>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
|
@ -0,0 +1,19 @@
|
|||
namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
|
||||
{
|
||||
public partial class ClipCornerRadiusGallery : ContentPage
|
||||
{
|
||||
public ClipCornerRadiusGallery()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
void OnCornerChanged(object sender, ValueChangedEventArgs e)
|
||||
{
|
||||
RoundRectangleGeometry.CornerRadius = new CornerRadius(
|
||||
TopLeftCorner.Value,
|
||||
TopRightCorner.Value,
|
||||
BottomLeftCorner.Value,
|
||||
BottomRightCorner.Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ namespace Xamarin.Forms.Controls.GalleryPages.ShapesGalleries
|
|||
GalleryBuilder.NavButton("Path Transform using string (TypeConverter) Gallery", () => new PathTransformStringGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Animate Shape Gallery", () => new AnimateShapeGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip Gallery", () => new ClipGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip CornerRadius Gallery", () => new ClipCornerRadiusGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip Views Gallery", () => new ClipViewsGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Add/Remove Clip Gallery", () => new AddRemoveClipGallery(), Navigation),
|
||||
GalleryBuilder.NavButton("Clip Performance Gallery", () => new ClipPerformanceGallery(), Navigation)
|
||||
|
|
|
@ -130,4 +130,8 @@
|
|||
</ShellSection>
|
||||
</ShellItem>
|
||||
<MenuItem Shell.MenuItemTemplate="{StaticResource MenuItemTemplateWithHeight}" Text="Height 200"/>
|
||||
|
||||
<MenuItem Clicked="OnToggleNavigatingDeferral" Text="Toggle Navigating Deferral">
|
||||
|
||||
</MenuItem>
|
||||
</localTest:TestShell>
|
||||
|
|
|
@ -47,6 +47,34 @@ namespace Xamarin.Forms.Controls.XamStore
|
|||
Routing.RegisterRoute("demo/demo", typeof(DemoShellPage));
|
||||
}
|
||||
|
||||
bool _defernavigationWithAlert;
|
||||
private void OnToggleNavigatingDeferral(object sender, System.EventArgs e)
|
||||
{
|
||||
_defernavigationWithAlert = !_defernavigationWithAlert;
|
||||
FlyoutIsPresented = false;
|
||||
}
|
||||
|
||||
protected override async void OnNavigating(ShellNavigatingEventArgs args)
|
||||
{
|
||||
base.OnNavigating(args);
|
||||
|
||||
if(_defernavigationWithAlert)
|
||||
{
|
||||
var token = args.GetDeferral();
|
||||
|
||||
var result = await DisplayActionSheet(
|
||||
"Are you sure?",
|
||||
"cancel",
|
||||
"destruction",
|
||||
"Yes", "No");
|
||||
|
||||
if (result != "Yes")
|
||||
args.Cancel();
|
||||
|
||||
token.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//bool allow = false;
|
||||
|
|
|
@ -69,9 +69,21 @@
|
|||
<EmbeddedResource Update="GalleryPages\DragAndDropGalleries\DragPaths.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\RadioButtonGalleries\ContentProperties.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\RadioButtonGalleries\RadioButtonGroupBindingGallery.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\RadioButtonGalleries\RadioButtonGroupGallery.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\RadioButtonGalleries\RadioButtonGroupGalleryPage.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<Compile Update="GalleryPages\RadioButtonGalleries\RadioButtonGroupGallery.xaml.cs">
|
||||
<DependentUpon>RadioButtonGroupGallery.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="TabIndexTest\DaysOfWeekView.xaml.cs">
|
||||
<DependentUpon>DaysOfWeekView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -80,6 +92,12 @@
|
|||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="GalleryPages\RadioButtonGalleries\ScatteredRadioButtonGallery.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="GalleryPages\RadioButtonGalleries\TemplateFromStyle.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="TabIndexTest\DayView.xaml">
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
|
|
|
@ -119,7 +119,8 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
[Test]
|
||||
public void NullConstructor ()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException> (() => new ControlTemplate (null));
|
||||
Func<object> func = null;
|
||||
Assert.Throws<ArgumentNullException> (() => new ControlTemplate (func));
|
||||
}
|
||||
|
||||
class TestPage : ContentPage
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
{
|
||||
base.Setup();
|
||||
Device.PlatformServices = new MockPlatformServices();
|
||||
Device.SetFlags(new[] { ExperimentalFlags.RadioButtonExperimental });
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
{
|
||||
base.Setup();
|
||||
Device.PlatformServices = new MockPlatformServices();
|
||||
Device.SetFlags(new[] { ExperimentalFlags.RadioButtonExperimental });
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
|
|
@ -59,5 +59,53 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
|
||||
Assert.AreNotEqual(0, points.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoundLineGeometryConstruction()
|
||||
{
|
||||
var lineGeometry = new LineGeometry(new Point(0, 0), new Point(100, 100));
|
||||
|
||||
Assert.IsNotNull(lineGeometry);
|
||||
Assert.AreEqual(0, lineGeometry.StartPoint.X);
|
||||
Assert.AreEqual(0, lineGeometry.StartPoint.Y);
|
||||
Assert.AreEqual(100, lineGeometry.EndPoint.X);
|
||||
Assert.AreEqual(100, lineGeometry.EndPoint.Y);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEllipseGeometryConstruction()
|
||||
{
|
||||
var ellipseGeometry = new EllipseGeometry(new Point(50, 50), 10, 20);
|
||||
|
||||
Assert.IsNotNull(ellipseGeometry);
|
||||
Assert.AreEqual(50, ellipseGeometry.Center.X);
|
||||
Assert.AreEqual(50, ellipseGeometry.Center.Y);
|
||||
Assert.AreEqual(10, ellipseGeometry.RadiusX);
|
||||
Assert.AreEqual(20, ellipseGeometry.RadiusY);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRectangleGeometryConstruction()
|
||||
{
|
||||
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 150, 150));
|
||||
|
||||
Assert.IsNotNull(rectangleGeometry);
|
||||
Assert.AreEqual(150, rectangleGeometry.Rect.Height);
|
||||
Assert.AreEqual(150, rectangleGeometry.Rect.Width);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoundRectangleGeometryConstruction()
|
||||
{
|
||||
var roundRectangleGeometry = new RoundRectangleGeometry(new CornerRadius(12, 0, 0, 12), new Rect(0, 0, 150, 150));
|
||||
|
||||
Assert.IsNotNull(roundRectangleGeometry);
|
||||
Assert.AreEqual(12, roundRectangleGeometry.CornerRadius.TopLeft);
|
||||
Assert.AreEqual(0, roundRectangleGeometry.CornerRadius.TopRight);
|
||||
Assert.AreEqual(0, roundRectangleGeometry.CornerRadius.BottomLeft);
|
||||
Assert.AreEqual(12, roundRectangleGeometry.CornerRadius.BottomRight);
|
||||
Assert.AreEqual(150, roundRectangleGeometry.Rect.Height);
|
||||
Assert.AreEqual(150, roundRectangleGeometry.Rect.Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,7 @@ namespace Xamarin.Forms.Markup.UnitTests
|
|||
{ typeof(PolyQuadraticBezierSegment), tbd },
|
||||
{ typeof(QuadraticBezierSegment), tbd },
|
||||
{ typeof(RectangleGeometry), tbd },
|
||||
{ typeof(RoundRectangleGeometry), tbd },
|
||||
{ typeof(RotateTransform), tbd },
|
||||
{ typeof(ScaleTransform), tbd },
|
||||
{ typeof(SkewTransform), tbd },
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
using NUnit.Framework;
|
||||
|
||||
namespace Xamarin.Forms.Core.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class RadioButtonTests : BaseTestFixture
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Device.SetFlags(new[]
|
||||
{
|
||||
ExperimentalFlags.RadioButtonExperimental
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RadioButtonAddedToGroupGetsGroupName()
|
||||
{
|
||||
var layout = new StackLayout();
|
||||
var groupName = "foo";
|
||||
var radioButton = new RadioButton();
|
||||
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, groupName);
|
||||
layout.Children.Add(radioButton);
|
||||
|
||||
Assert.That(radioButton.GroupName, Is.EqualTo(groupName));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NestedRadioButtonAddedToGroupGetsGroupName()
|
||||
{
|
||||
var layout = new StackLayout();
|
||||
var groupName = "foo";
|
||||
var radioButton = new RadioButton();
|
||||
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, groupName);
|
||||
|
||||
|
||||
var grid = new Grid();
|
||||
grid.Children.Add(radioButton);
|
||||
layout.Children.Add(grid);
|
||||
|
||||
Assert.That(radioButton.GroupName, Is.EqualTo(groupName));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RadioButtonAddedToGroupKeepsGroupName()
|
||||
{
|
||||
var layout = new StackLayout();
|
||||
var groupName = "foo";
|
||||
var oldName = "bar";
|
||||
var radioButton = new RadioButton() { GroupName = oldName };
|
||||
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, groupName);
|
||||
layout.Children.Add(radioButton);
|
||||
|
||||
Assert.That(radioButton.GroupName, Is.EqualTo(oldName));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LayoutGroupNameAppliesToExistingRadioButtons()
|
||||
{
|
||||
var layout = new StackLayout();
|
||||
var groupName = "foo";
|
||||
var radioButton = new RadioButton();
|
||||
|
||||
layout.Children.Add(radioButton);
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, groupName);
|
||||
|
||||
Assert.That(radioButton.GroupName, Is.EqualTo(groupName));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdatedGroupNameAppliesToRadioButtonsWithOldGroupName()
|
||||
{
|
||||
var layout = new StackLayout();
|
||||
var groupName = "foo";
|
||||
var updatedGroupName = "bar";
|
||||
var otherGroupName = "other";
|
||||
var radioButton1 = new RadioButton();
|
||||
var radioButton2 = new RadioButton() { GroupName = otherGroupName };
|
||||
|
||||
layout.Children.Add(radioButton1);
|
||||
layout.Children.Add(radioButton2);
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, groupName);
|
||||
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, updatedGroupName);
|
||||
|
||||
Assert.That(radioButton1.GroupName, Is.EqualTo(updatedGroupName));
|
||||
Assert.That(radioButton2.GroupName, Is.EqualTo("other"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThereCanBeOnlyOne()
|
||||
{
|
||||
var groupName = "foo";
|
||||
|
||||
var radioButton1 = new RadioButton() { GroupName = groupName };
|
||||
var radioButton2 = new RadioButton() { GroupName = groupName };
|
||||
var radioButton3 = new RadioButton() { GroupName = groupName };
|
||||
var radioButton4 = new RadioButton() { GroupName = groupName };
|
||||
|
||||
var layout = new Grid();
|
||||
|
||||
layout.Children.Add(radioButton1);
|
||||
layout.Children.Add(radioButton2);
|
||||
layout.Children.Add(radioButton3);
|
||||
layout.Children.Add(radioButton4);
|
||||
|
||||
radioButton1.IsChecked = true;
|
||||
|
||||
Assert.IsTrue(radioButton1.IsChecked);
|
||||
Assert.IsFalse(radioButton2.IsChecked);
|
||||
Assert.IsFalse(radioButton3.IsChecked);
|
||||
Assert.IsFalse(radioButton4.IsChecked);
|
||||
|
||||
radioButton3.IsChecked = true;
|
||||
|
||||
Assert.IsFalse(radioButton1.IsChecked);
|
||||
Assert.IsFalse(radioButton2.IsChecked);
|
||||
Assert.IsTrue(radioButton3.IsChecked);
|
||||
Assert.IsFalse(radioButton4.IsChecked);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ImpliedGroup()
|
||||
{
|
||||
var radioButton1 = new RadioButton();
|
||||
var radioButton2 = new RadioButton();
|
||||
var radioButton3 = new RadioButton();
|
||||
|
||||
var layout = new Grid();
|
||||
|
||||
layout.Children.Add(radioButton1);
|
||||
layout.Children.Add(radioButton2);
|
||||
layout.Children.Add(radioButton3);
|
||||
|
||||
radioButton1.IsChecked = true;
|
||||
|
||||
Assert.IsTrue(radioButton1.IsChecked);
|
||||
Assert.IsFalse(radioButton2.IsChecked);
|
||||
Assert.IsFalse(radioButton3.IsChecked);
|
||||
|
||||
radioButton3.IsChecked = true;
|
||||
|
||||
Assert.IsFalse(radioButton1.IsChecked);
|
||||
Assert.IsFalse(radioButton2.IsChecked);
|
||||
Assert.IsTrue(radioButton3.IsChecked);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ImpliedGroupDoesNotIncludeExplicitGroups()
|
||||
{
|
||||
var radioButton1 = new RadioButton();
|
||||
var radioButton2 = new RadioButton();
|
||||
var radioButton3 = new RadioButton() { GroupName = "foo" };
|
||||
|
||||
var layout = new Grid();
|
||||
|
||||
layout.Children.Add(radioButton1);
|
||||
layout.Children.Add(radioButton2);
|
||||
layout.Children.Add(radioButton3);
|
||||
|
||||
radioButton1.IsChecked = true;
|
||||
radioButton3.IsChecked = true;
|
||||
|
||||
Assert.IsTrue(radioButton1.IsChecked);
|
||||
Assert.IsFalse(radioButton2.IsChecked);
|
||||
Assert.IsTrue(radioButton3.IsChecked);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemovingSelectedButtonFromGroupClearsSelection()
|
||||
{
|
||||
var radioButton1 = new RadioButton() { GroupName = "foo" };
|
||||
var radioButton2 = new RadioButton() { GroupName = "foo" };
|
||||
var radioButton3 = new RadioButton() { GroupName = "foo" };
|
||||
|
||||
radioButton1.IsChecked = true;
|
||||
radioButton2.IsChecked = true;
|
||||
|
||||
Assert.IsFalse(radioButton1.IsChecked);
|
||||
Assert.IsTrue(radioButton2.IsChecked);
|
||||
Assert.IsFalse(radioButton3.IsChecked);
|
||||
|
||||
radioButton2.GroupName = "bar";
|
||||
|
||||
Assert.IsFalse(radioButton1.IsChecked);
|
||||
Assert.IsTrue(radioButton2.IsChecked);
|
||||
Assert.IsFalse(radioButton3.IsChecked);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GroupControllerSelectionIsNullWhenSelectedButtonRemoved()
|
||||
{
|
||||
var layout = new Grid();
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, "foo");
|
||||
var selected = layout.GetValue(RadioButtonGroup.SelectedValueProperty);
|
||||
|
||||
Assert.Null(selected);
|
||||
|
||||
var radioButton1 = new RadioButton() { Value = 1 };
|
||||
var radioButton2 = new RadioButton() { Value = 2 };
|
||||
var radioButton3 = new RadioButton() { Value = 3 };
|
||||
|
||||
layout.Children.Add(radioButton1);
|
||||
layout.Children.Add(radioButton2);
|
||||
layout.Children.Add(radioButton3);
|
||||
|
||||
Assert.Null(selected);
|
||||
|
||||
radioButton1.IsChecked = true;
|
||||
|
||||
selected = layout.GetValue(RadioButtonGroup.SelectedValueProperty);
|
||||
|
||||
Assert.AreEqual(selected, 1);
|
||||
|
||||
Assert.AreEqual(radioButton1.GroupName, "foo");
|
||||
radioButton1.GroupName = "bar";
|
||||
|
||||
selected = layout.GetValue(RadioButtonGroup.SelectedValueProperty);
|
||||
Assert.Null(selected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GroupSelectedValueUpdatesWhenSelectedButtonValueUpdates()
|
||||
{
|
||||
var layout = new Grid();
|
||||
layout.SetValue(RadioButtonGroup.GroupNameProperty, "foo");
|
||||
|
||||
var radioButton1 = new RadioButton() { Value = 1, IsChecked = true };
|
||||
var radioButton2 = new RadioButton() { Value = 2 };
|
||||
var radioButton3 = new RadioButton() { Value = 3 };
|
||||
|
||||
layout.Children.Add(radioButton1);
|
||||
layout.Children.Add(radioButton2);
|
||||
layout.Children.Add(radioButton3);
|
||||
|
||||
Assert.AreEqual(1, layout.GetValue(RadioButtonGroup.SelectedValueProperty));
|
||||
|
||||
radioButton1.Value = "updated";
|
||||
|
||||
Assert.AreEqual("updated", layout.GetValue(RadioButtonGroup.SelectedValueProperty));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -348,20 +348,21 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
lifeCycleState.AllTrue();
|
||||
|
||||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
|
||||
//if you're just pushing a page then the section and item are still visible but the content is not
|
||||
Assert.IsFalse(lifeCycleState.PageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ShellContentPageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ContentAppearing);
|
||||
Assert.IsTrue(lifeCycleState.SectionAppearing);
|
||||
Assert.IsTrue(lifeCycleState.ItemAppearing);
|
||||
|
||||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
Assert.IsFalse(lifeCycleState.PageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ShellContentPageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ContentAppearing);
|
||||
Assert.IsTrue(lifeCycleState.SectionAppearing);
|
||||
Assert.IsTrue(lifeCycleState.ItemAppearing);
|
||||
|
||||
await shell.Navigation.PopAsync();
|
||||
Assert.IsFalse(lifeCycleState.PageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ShellContentPageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ContentAppearing);
|
||||
Assert.IsTrue(lifeCycleState.SectionAppearing);
|
||||
Assert.IsTrue(lifeCycleState.ItemAppearing);
|
||||
|
@ -383,7 +384,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
Assert.IsFalse(lifeCycleState.PageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ShellContentPageAppearing);
|
||||
Assert.IsFalse(lifeCycleState.ContentAppearing);
|
||||
Assert.IsTrue(lifeCycleState.SectionAppearing);
|
||||
Assert.IsTrue(lifeCycleState.ItemAppearing);
|
||||
|
@ -582,7 +583,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
set;
|
||||
}
|
||||
|
||||
public bool PageAppearing
|
||||
public bool ShellContentPageAppearing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
|
@ -597,13 +598,14 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
shell.SearchForRoute(SectionRoute).Appearing += (_, __) => SectionAppearing = true;
|
||||
shell.SearchForRoute(ContentRoute).Appearing += (_, __) => ContentAppearing = true;
|
||||
shellContent.Appearing += (_, __) => ContentAppearing = true;
|
||||
contentPage.Appearing += (_, __) => PageAppearing = true;
|
||||
contentPage.Appearing += (_, __) => ShellContentPageAppearing = true;
|
||||
|
||||
shell.SearchForRoute(ItemRoute).Disappearing += (_, __) => ItemAppearing = false;
|
||||
shell.SearchForRoute(SectionRoute).Disappearing += (_, __) => SectionAppearing = false;
|
||||
shellContent.Disappearing += (_, __) => ContentAppearing = false;
|
||||
contentPage.Disappearing += (_, __) => PageAppearing = false;
|
||||
contentPage.Disappearing += (_, __) => ShellContentPageAppearing = false;
|
||||
}
|
||||
|
||||
public ShellLifeCycleState(BaseShellItem baseShellItem)
|
||||
{
|
||||
var shellContent = baseShellItem.SearchForRoute<ShellContent>(ContentRoute);
|
||||
|
@ -612,12 +614,12 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
baseShellItem.SearchForRoute(ItemRoute).Appearing += (_, __) => ItemAppearing = true;
|
||||
baseShellItem.SearchForRoute(SectionRoute).Appearing += (_, __) => SectionAppearing = true;
|
||||
shellContent.Appearing += (_, __) => ContentAppearing = true;
|
||||
contentPage.Appearing += (_, __) => PageAppearing = true;
|
||||
contentPage.Appearing += (_, __) => ShellContentPageAppearing = true;
|
||||
|
||||
baseShellItem.SearchForRoute(ItemRoute).Disappearing += (_, __) => ItemAppearing = false;
|
||||
baseShellItem.SearchForRoute(SectionRoute).Disappearing += (_, __) => SectionAppearing = false;
|
||||
shellContent.Disappearing += (_, __) => ContentAppearing = false;
|
||||
contentPage.Disappearing += (_, __) => PageAppearing = false;
|
||||
contentPage.Disappearing += (_, __) => ShellContentPageAppearing = false;
|
||||
}
|
||||
|
||||
public void AllFalse()
|
||||
|
@ -625,7 +627,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.IsFalse(ItemAppearing);
|
||||
Assert.IsFalse(SectionAppearing);
|
||||
Assert.IsFalse(ContentAppearing);
|
||||
Assert.IsFalse(PageAppearing);
|
||||
Assert.IsFalse(ShellContentPageAppearing);
|
||||
}
|
||||
|
||||
public void AllTrue()
|
||||
|
@ -633,7 +635,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.IsTrue(ItemAppearing);
|
||||
Assert.IsTrue(SectionAppearing);
|
||||
Assert.IsTrue(ContentAppearing);
|
||||
Assert.IsTrue(PageAppearing);
|
||||
Assert.IsTrue(ShellContentPageAppearing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Xamarin.Forms.Core.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ShellNavigatingTests : ShellTestBase
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void CancelNavigation()
|
||||
{
|
||||
var shell = new Shell();
|
||||
|
||||
var one = new ShellItem { Route = "one" };
|
||||
var two = new ShellItem { Route = "two" };
|
||||
|
||||
var tabone = MakeSimpleShellSection("tabone", "content");
|
||||
var tabtwo = MakeSimpleShellSection("tabtwo", "content");
|
||||
var tabthree = MakeSimpleShellSection("tabthree", "content");
|
||||
var tabfour = MakeSimpleShellSection("tabfour", "content");
|
||||
|
||||
one.Items.Add(tabone);
|
||||
one.Items.Add(tabtwo);
|
||||
|
||||
two.Items.Add(tabthree);
|
||||
two.Items.Add(tabfour);
|
||||
|
||||
shell.Items.Add(one);
|
||||
shell.Items.Add(two);
|
||||
|
||||
Assume.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//one/tabone/content"));
|
||||
|
||||
shell.Navigating += (s, e) =>
|
||||
{
|
||||
e.Cancel();
|
||||
};
|
||||
|
||||
shell.GoToAsync(new ShellNavigationState("//two/tabfour/"));
|
||||
|
||||
Assume.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//one/tabone/content"));
|
||||
}
|
||||
|
||||
[TestCase("PopToRoot")]
|
||||
[TestCase("Pop")]
|
||||
public async Task DeferPopNavigation(string testCase)
|
||||
{
|
||||
TestShell shell = new TestShell()
|
||||
{
|
||||
Items = { CreateShellItem<FlyoutItem>() }
|
||||
};
|
||||
|
||||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
|
||||
ShellNavigatingDeferral _token = null;
|
||||
shell.Navigating += async (_, args) =>
|
||||
{
|
||||
_token = args.GetDeferral();
|
||||
await Task.Delay(500);
|
||||
Assert.AreEqual(3, shell.Navigation.NavigationStack.Count);
|
||||
_token.Complete();
|
||||
};
|
||||
|
||||
var source = new TaskCompletionSource<object>();
|
||||
shell.Navigated += (_, args) =>
|
||||
{
|
||||
source.SetResult(true);
|
||||
};
|
||||
|
||||
if (testCase == "Pop")
|
||||
{
|
||||
await shell.Navigation.PopAsync();
|
||||
await source.Task;
|
||||
Assert.AreEqual(2, shell.Navigation.NavigationStack.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
await shell.Navigation.PopToRootAsync();
|
||||
await source.Task;
|
||||
Assert.AreEqual(1, shell.Navigation.NavigationStack.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("PopToRoot")]
|
||||
[TestCase("Pop")]
|
||||
[TestCase("GoToAsync")]
|
||||
[TestCase("Push")]
|
||||
public async Task NavigationTaskCompletesAfterDeferalHasFinished(string testCase)
|
||||
{
|
||||
Routing.RegisterRoute(nameof(NavigationTaskCompletesAfterDeferalHasFinished), typeof(ContentPage));
|
||||
var shell = new TestShell()
|
||||
{
|
||||
Items = { CreateShellItem<FlyoutItem>() }
|
||||
};
|
||||
|
||||
ShellNavigatingDeferral _token = null;
|
||||
shell.Navigating += async (_, args) =>
|
||||
{
|
||||
_token = args.GetDeferral();
|
||||
await Task.Delay(500);
|
||||
_token.Complete();
|
||||
};
|
||||
|
||||
switch (testCase)
|
||||
{
|
||||
case "PopToRoot":
|
||||
await shell.Navigation.PopToRootAsync();
|
||||
break;
|
||||
case "Pop":
|
||||
await shell.Navigation.PopAsync();
|
||||
break;
|
||||
case "GoToAsync":
|
||||
await shell.GoToAsync(nameof(NavigationTaskCompletesAfterDeferalHasFinished));
|
||||
break;
|
||||
case "Push":
|
||||
await shell.Navigation.PushAsync(new ContentPage());
|
||||
break;
|
||||
}
|
||||
|
||||
Assert.IsTrue(_token.IsCompleted);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CompletingTheSameDeferalTokenTwiceDoesntDoAnything()
|
||||
{
|
||||
var args = CreateShellNavigatedEventArgs();
|
||||
var token = args.GetDeferral();
|
||||
args.GetDeferral();
|
||||
|
||||
Assert.AreEqual(2, args.DeferralCount);
|
||||
token.Complete();
|
||||
Assert.AreEqual(1, args.DeferralCount);
|
||||
token.Complete();
|
||||
Assert.AreEqual(1, args.DeferralCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DotDotNavigateBackFromPagesWithDefaultRoute()
|
||||
{
|
||||
var flyoutItem = CreateShellItem<FlyoutItem>();
|
||||
var itemRoute = Routing.GetRoute(flyoutItem.CurrentItem.CurrentItem);
|
||||
var page1 = new ContentPage();
|
||||
var page2 = new ContentPage();
|
||||
TestShell shell = new TestShell()
|
||||
{
|
||||
Items = { flyoutItem }
|
||||
};
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}"));
|
||||
|
||||
await shell.Navigation.PushAsync(page1);
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}/{Routing.GetRoute(page1)}"));
|
||||
|
||||
await shell.Navigation.PushAsync(page2);
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}/{Routing.GetRoute(page1)}/{Routing.GetRoute(page2)}"));
|
||||
|
||||
await shell.GoToAsync("..");
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}/{Routing.GetRoute(page1)}"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NavigationPushAndPopBasic()
|
||||
{
|
||||
var flyoutItem =
|
||||
CreateShellItem<FlyoutItem>(
|
||||
shellItemRoute: "item",
|
||||
shellSectionRoute: "section",
|
||||
shellContentRoute: "content"
|
||||
);
|
||||
|
||||
var itemRoute = "item/section/content";
|
||||
var page1 = new ContentPage();
|
||||
var page2 = new ContentPage();
|
||||
var shell = new TestShell(flyoutItem);
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}"));
|
||||
|
||||
await shell.Navigation.PushAsync(page1);
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}/{Routing.GetRoute(page1)}"));
|
||||
|
||||
await shell.Navigation.PushAsync(page2);
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}/{Routing.GetRoute(page1)}/{Routing.GetRoute(page2)}"));
|
||||
|
||||
await shell.Navigation.PopAsync();
|
||||
|
||||
Assert.That(shell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{itemRoute}/{Routing.GetRoute(page1)}"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NavigateToDefaultShellContent()
|
||||
{
|
||||
TestShell testShell = new TestShell(CreateShellItem<FlyoutItem>());
|
||||
var page = new ContentPage();
|
||||
|
||||
var contentRoute = testShell.CurrentItem.CurrentItem.CurrentItem.Route;
|
||||
var pageRoute = Routing.GetRoute(page);
|
||||
|
||||
await testShell.Navigation.PushAsync(new ContentPage());
|
||||
await testShell.Navigation.PushAsync(page);
|
||||
|
||||
await testShell.GoToAsync($"//{contentRoute}/{pageRoute}");
|
||||
|
||||
|
||||
Assert.That(testShell.CurrentState.Location.ToString(),
|
||||
Is.EqualTo($"//{contentRoute}/{pageRoute}"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PopToRootWithMultipleFlyoutItems()
|
||||
{
|
||||
TestShell testShell = new TestShell(
|
||||
CreateShellItem<FlyoutItem>(shellItemRoute:"store", shellContentRoute: "home"),
|
||||
CreateShellItem<FlyoutItem>(shellItemRoute:"second", shellContentRoute: "home")
|
||||
);
|
||||
|
||||
await testShell.Navigation.PushAsync(new ContentPage());
|
||||
await testShell.Navigation.PushAsync(new ContentPage());
|
||||
await testShell.Navigation.PopToRootAsync();
|
||||
}
|
||||
|
||||
ShellNavigatingEventArgs CreateShellNavigatedEventArgs() =>
|
||||
new ShellNavigatingEventArgs("..", "../newstate", ShellNavigationSource.Push, true);
|
||||
}
|
||||
}
|
|
@ -244,6 +244,41 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
return (item as IShellController).GetItems();
|
||||
}
|
||||
|
||||
|
||||
public class TestFlyoutItem : FlyoutItem
|
||||
{
|
||||
public TestFlyoutItem()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TestFlyoutItem(ShellSection shellSection)
|
||||
{
|
||||
Items.Add(shellSection);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestShellSection : ShellSection
|
||||
{
|
||||
public TestShellSection()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TestShellSection(ShellContent shellContent)
|
||||
{
|
||||
Items.Add(shellContent);
|
||||
}
|
||||
|
||||
public bool? LastPopWasAnimated { get; private set; }
|
||||
|
||||
protected override Task<Page> OnPopAsync(bool animated)
|
||||
{
|
||||
LastPopWasAnimated = animated;
|
||||
return base.OnPopAsync(animated);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestShell : Shell
|
||||
{
|
||||
public int OnNavigatedCount;
|
||||
|
@ -251,6 +286,8 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
public int NavigatedCount;
|
||||
public int NavigatingCount;
|
||||
public int OnBackButtonPressedCount;
|
||||
public ShellNavigatedEventArgs LastShellNavigatedEventArgs;
|
||||
public ShellNavigatingEventArgs LastShellNavigatingEventArgs;
|
||||
|
||||
public TestShell()
|
||||
{
|
||||
|
@ -258,9 +295,15 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
this.Navigating += (_, __) => NavigatingCount++;
|
||||
}
|
||||
|
||||
public TestShell(params ShellItem[] shellItems) : this()
|
||||
{
|
||||
shellItems.ForEach(x => Items.Add(x));
|
||||
}
|
||||
|
||||
public Action<ShellNavigatedEventArgs> OnNavigatedHandler { get; set; }
|
||||
protected override void OnNavigated(ShellNavigatedEventArgs args)
|
||||
{
|
||||
LastShellNavigatedEventArgs = args;
|
||||
base.OnNavigated(args);
|
||||
OnNavigatedHandler?.Invoke(args);
|
||||
OnNavigatedCount++;
|
||||
|
@ -268,6 +311,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
|
||||
protected override void OnNavigating(ShellNavigatingEventArgs args)
|
||||
{
|
||||
LastShellNavigatingEventArgs = args;
|
||||
base.OnNavigating(args);
|
||||
OnNavigatingCount++;
|
||||
}
|
||||
|
@ -301,6 +345,15 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.AreEqual(count, OnNavigatingCount, $"OnNavigatingCount: {message}");
|
||||
Assert.AreEqual(count, NavigatedCount, $"NavigatedCount: {message}");
|
||||
}
|
||||
|
||||
|
||||
public bool? LastPopWasAnimated
|
||||
{
|
||||
get
|
||||
{
|
||||
return (CurrentItem.CurrentItem as TestShellSection)?.LastPopWasAnimated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -368,6 +368,18 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
*/
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task DotDotAdheresToAnimationParameter()
|
||||
{
|
||||
Routing.RegisterRoute(nameof(DotDotAdheresToAnimationParameter), typeof(ContentPage));
|
||||
var shellContent = new ShellContent();
|
||||
var shell = new TestShell(new TestFlyoutItem(new TestShellSection(shellContent)));
|
||||
await shell.GoToAsync(nameof(DotDotAdheresToAnimationParameter));
|
||||
await shell.GoToAsync("..", true);
|
||||
Assert.IsTrue(shell.LastPopWasAnimated);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DefaultRoutesMaintainedIfThatsAllThereIs()
|
||||
{
|
||||
|
@ -596,40 +608,6 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.AreEqual("1234", (two.CurrentItem.CurrentItem.Content as ShellTestPage).SomeQueryParameter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CancelNavigation()
|
||||
{
|
||||
var shell = new Shell();
|
||||
|
||||
var one = new ShellItem { Route = "one" };
|
||||
var two = new ShellItem { Route = "two" };
|
||||
|
||||
var tabone = MakeSimpleShellSection("tabone", "content");
|
||||
var tabtwo = MakeSimpleShellSection("tabtwo", "content");
|
||||
var tabthree = MakeSimpleShellSection("tabthree", "content");
|
||||
var tabfour = MakeSimpleShellSection("tabfour", "content");
|
||||
|
||||
one.Items.Add(tabone);
|
||||
one.Items.Add(tabtwo);
|
||||
|
||||
two.Items.Add(tabthree);
|
||||
two.Items.Add(tabfour);
|
||||
|
||||
shell.Items.Add(one);
|
||||
shell.Items.Add(two);
|
||||
|
||||
Assume.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//one/tabone/content"));
|
||||
|
||||
shell.Navigating += (s, e) =>
|
||||
{
|
||||
e.Cancel();
|
||||
};
|
||||
|
||||
shell.GoToAsync(new ShellNavigationState("//two/tabfour/"));
|
||||
|
||||
Assume.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//one/tabone/content"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnBackbuttonPressedPageReturnsTrue()
|
||||
{
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
<Compile Include="Markup\VisualElementExtensionsTests.cs" />
|
||||
<Compile Include="MarshalingObservableCollectionTests.cs" />
|
||||
<Compile Include="NumericExtensionsTests.cs" />
|
||||
<Compile Include="RadioButtonTests.cs" />
|
||||
<Compile Include="RefreshViewTests.cs" />
|
||||
<Compile Include="MockDispatcherProvider.cs" />
|
||||
<Compile Include="MockDispatcher.cs" />
|
||||
|
@ -109,6 +110,7 @@
|
|||
<Compile Include="ShellElementCollectionTests.cs" />
|
||||
<Compile Include="ShellFlyoutItemTemplateTests.cs" />
|
||||
<Compile Include="ShellModalTests.cs" />
|
||||
<Compile Include="ShellNavigatingTests.cs" />
|
||||
<Compile Include="ShellTestBase.cs" />
|
||||
<Compile Include="ShellLifeCycleTests.cs" />
|
||||
<Compile Include="ShellUriHandlerTests.cs" />
|
||||
|
@ -260,6 +262,7 @@
|
|||
<Compile Include="RadialGradientBrushTests.cs" />
|
||||
<Compile Include="SwipeViewTests.cs" />
|
||||
<Compile Include="PathSegmentTests.cs" />
|
||||
<Compile Include="GeometryTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
internal class ContentConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is View view)
|
||||
{
|
||||
return ConfigureView(view);
|
||||
}
|
||||
|
||||
if (value is string textContent)
|
||||
{
|
||||
return ConvertToLabel(textContent);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
View ConfigureView(View view)
|
||||
{
|
||||
if (view is ITextElement)
|
||||
{
|
||||
BindTextProperties(view);
|
||||
}
|
||||
|
||||
if (view is IFontElement)
|
||||
{
|
||||
BindFontProperties(view);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
Label ConvertToLabel(string textContent)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Text = textContent
|
||||
};
|
||||
|
||||
BindTextProperties(label);
|
||||
BindFontProperties(label);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
static void BindTextProperties(BindableObject content)
|
||||
{
|
||||
BindProperty(content, TextElement.TextColorProperty, typeof(ITextElement));
|
||||
BindProperty(content, TextElement.CharacterSpacingProperty, typeof(ITextElement));
|
||||
BindProperty(content, TextElement.TextTransformProperty, typeof(ITextElement));
|
||||
}
|
||||
|
||||
static void BindFontProperties(BindableObject content)
|
||||
{
|
||||
BindProperty(content, FontElement.FontAttributesProperty, typeof(IFontElement));
|
||||
BindProperty(content, FontElement.FontSizeProperty, typeof(IFontElement));
|
||||
BindProperty(content, FontElement.FontFamilyProperty, typeof(IFontElement));
|
||||
}
|
||||
|
||||
static void BindProperty(BindableObject content, BindableProperty property, Type type)
|
||||
{
|
||||
if (content.IsSet(property) || content.GetIsBound(property))
|
||||
{
|
||||
// Don't override the property if user has already set it
|
||||
return;
|
||||
}
|
||||
|
||||
content.SetBinding(property,
|
||||
new Binding(property.PropertyName,
|
||||
source: new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, type)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,13 @@ namespace Xamarin.Forms
|
|||
{
|
||||
public class ContentPresenter : Layout
|
||||
{
|
||||
public static BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(View), typeof(ContentPresenter), null, propertyChanged: OnContentChanged);
|
||||
public static BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View),
|
||||
typeof(ContentPresenter), null, propertyChanged: OnContentChanged);
|
||||
|
||||
public ContentPresenter()
|
||||
{
|
||||
SetBinding(ContentProperty, new Binding("Content", source: RelativeBindingSource.TemplatedParent));
|
||||
SetBinding(ContentProperty, new Binding(ContentProperty.PropertyName, source: RelativeBindingSource.TemplatedParent,
|
||||
converter: new ContentConverter()));
|
||||
}
|
||||
|
||||
public View Content
|
||||
|
|
|
@ -11,5 +11,9 @@ namespace Xamarin.Forms
|
|||
public ControlTemplate(Type type) : base(type)
|
||||
{
|
||||
}
|
||||
|
||||
public ControlTemplate(Func<object> createTemplate) : base(createTemplate)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,8 @@ namespace Xamarin.Forms
|
|||
{
|
||||
public static readonly BindableProperty BackButtonTitleProperty = BindableProperty.CreateAttached("BackButtonTitle", typeof(string), typeof(Page), null);
|
||||
|
||||
public static readonly BindableProperty HasNavigationBarProperty = BindableProperty.CreateAttached("HasNavigationBar", typeof(bool), typeof(Page), true);
|
||||
public static readonly BindableProperty HasNavigationBarProperty =
|
||||
BindableProperty.CreateAttached("HasNavigationBar", typeof(bool), typeof(Page), true);
|
||||
|
||||
public static readonly BindableProperty HasBackButtonProperty = BindableProperty.CreateAttached("HasBackButton", typeof(bool), typeof(NavigationPage), true);
|
||||
|
||||
|
|
|
@ -1,32 +1,89 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xamarin.Forms.Platform;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.Shapes;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
[RenderWith(typeof(_RadioButtonRenderer))]
|
||||
public class RadioButton : Button, IElementConfiguration<RadioButton>
|
||||
public class RadioButton : TemplatedView, IElementConfiguration<RadioButton>, ITextElement, IFontElement, IBorderElement
|
||||
{
|
||||
public const string CheckedVisualState = "Checked";
|
||||
public const string UncheckedVisualState = "Unchecked";
|
||||
|
||||
public const string TemplateRootName = "Root";
|
||||
public const string CheckedIndicator = "CheckedIndicator";
|
||||
public const string UncheckedButton = "Button";
|
||||
|
||||
internal const string GroupNameChangedMessage = "RadioButtonGroupNameChanged";
|
||||
internal const string ValueChangedMessage = "RadioButtonValueChanged";
|
||||
|
||||
// Template Parts
|
||||
TapGestureRecognizer _tapGestureRecognizer;
|
||||
View _templateRoot;
|
||||
|
||||
static readonly Brush RadioButtonCheckMarkThemeColor = ResolveThemeColor("RadioButtonCheckMarkThemeColor");
|
||||
static readonly Brush RadioButtonThemeColor = ResolveThemeColor("RadioButtonThemeColor");
|
||||
static ControlTemplate s_defaultTemplate;
|
||||
|
||||
readonly Lazy<PlatformConfigurationRegistry<RadioButton>> _platformConfigurationRegistry;
|
||||
|
||||
static Dictionary<string, List<WeakReference<RadioButton>>> _groupNameToElements;
|
||||
|
||||
public const string IsCheckedVisualState = "IsChecked";
|
||||
|
||||
public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(
|
||||
nameof(IsChecked), typeof(bool), typeof(RadioButton), false, propertyChanged: (b, o, n) => ((RadioButton)b).OnIsCheckedPropertyChanged((bool)n), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public static readonly BindableProperty GroupNameProperty = BindableProperty.Create(
|
||||
nameof(GroupName), typeof(string), typeof(RadioButton), null, propertyChanged: (b, o, n) => ((RadioButton)b).OnGroupNamePropertyChanged((string)o, (string)n));
|
||||
|
||||
// TODO Needs implementations beyond Android
|
||||
//public static readonly BindableProperty ButtonSourceProperty = BindableProperty.Create(
|
||||
// nameof(ButtonSource), typeof(ImageSource), typeof(RadioButton), null);
|
||||
static bool? s_rendererAvailable;
|
||||
|
||||
public event EventHandler<CheckedChangedEventArgs> CheckedChanged;
|
||||
|
||||
public static readonly BindableProperty ContentProperty =
|
||||
BindableProperty.Create(nameof(Content), typeof(object), typeof(RadioButton), null);
|
||||
|
||||
public static readonly BindableProperty ValueProperty =
|
||||
BindableProperty.Create(nameof(Value), typeof(object), typeof(RadioButton), null,
|
||||
propertyChanged: (b, o, n) => ((RadioButton)b).OnValuePropertyChanged());
|
||||
|
||||
public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(
|
||||
nameof(IsChecked), typeof(bool), typeof(RadioButton), false,
|
||||
propertyChanged: (b, o, n) => ((RadioButton)b).OnIsCheckedPropertyChanged((bool)n),
|
||||
defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public static readonly BindableProperty GroupNameProperty = BindableProperty.Create(
|
||||
nameof(GroupName), typeof(string), typeof(RadioButton), null,
|
||||
propertyChanged: (b, o, n) => ((RadioButton)b).OnGroupNamePropertyChanged((string)o, (string)n));
|
||||
|
||||
public static readonly BindableProperty TextColorProperty = TextElement.TextColorProperty;
|
||||
|
||||
public static readonly BindableProperty CharacterSpacingProperty = TextElement.CharacterSpacingProperty;
|
||||
|
||||
public static readonly BindableProperty TextTransformProperty = TextElement.TextTransformProperty;
|
||||
|
||||
public static readonly BindableProperty FontAttributesProperty = FontElement.FontAttributesProperty;
|
||||
|
||||
public static readonly BindableProperty FontFamilyProperty = FontElement.FontFamilyProperty;
|
||||
|
||||
public static readonly BindableProperty FontSizeProperty = FontElement.FontSizeProperty;
|
||||
|
||||
public static readonly BindableProperty BorderColorProperty = BorderElement.BorderColorProperty;
|
||||
|
||||
public static readonly BindableProperty CornerRadiusProperty = BorderElement.CornerRadiusProperty;
|
||||
|
||||
public static readonly BindableProperty BorderWidthProperty = BorderElement.BorderWidthProperty;
|
||||
|
||||
// If Content is set to a string, the string will be displayed using the native Text property
|
||||
// on platforms which support that; in a ControlTemplate it will be automatically converted
|
||||
// to a Label. If Content is set to a View, the View will be displayed on platforms which
|
||||
// support Content natively or in the ContentPresenter of the ControlTemplate, if a ControlTemplate
|
||||
// is set. If a ControlTemplate is not set and the platform does not natively support arbitrary
|
||||
// Content, the ToString() representation of Content will be displayed.
|
||||
// For all types other than View and string, the ToString() representation of Content will be displayed.
|
||||
public object Content
|
||||
{
|
||||
get => GetValue(ContentProperty);
|
||||
set => SetValue(ContentProperty, value);
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get => GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public bool IsChecked
|
||||
{
|
||||
get { return (bool)GetValue(IsCheckedProperty); }
|
||||
|
@ -39,168 +96,438 @@ namespace Xamarin.Forms
|
|||
set { SetValue(GroupNameProperty, value); }
|
||||
}
|
||||
|
||||
// TODO Needs implementations beyond Android
|
||||
//public ImageSource ButtonSource
|
||||
//{
|
||||
// get { return (ImageSource)GetValue(ButtonSourceProperty); }
|
||||
// set { SetValue(ButtonSourceProperty, value); }
|
||||
//}
|
||||
public Color TextColor
|
||||
{
|
||||
get { return (Color)GetValue(TextColorProperty); }
|
||||
set { SetValue(TextColorProperty, value); }
|
||||
}
|
||||
|
||||
public double CharacterSpacing
|
||||
{
|
||||
get { return (double)GetValue(CharacterSpacingProperty); }
|
||||
set { SetValue(CharacterSpacingProperty, value); }
|
||||
}
|
||||
|
||||
public TextTransform TextTransform
|
||||
{
|
||||
get { return (TextTransform)GetValue(TextTransformProperty); }
|
||||
set { SetValue(TextTransformProperty, value); }
|
||||
}
|
||||
|
||||
public FontAttributes FontAttributes
|
||||
{
|
||||
get { return (FontAttributes)GetValue(FontAttributesProperty); }
|
||||
set { SetValue(FontAttributesProperty, value); }
|
||||
}
|
||||
|
||||
public string FontFamily
|
||||
{
|
||||
get { return (string)GetValue(FontFamilyProperty); }
|
||||
set { SetValue(FontFamilyProperty, value); }
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(FontSizeConverter))]
|
||||
public double FontSize
|
||||
{
|
||||
get { return (double)GetValue(FontSizeProperty); }
|
||||
set { SetValue(FontSizeProperty, value); }
|
||||
}
|
||||
|
||||
public double BorderWidth
|
||||
{
|
||||
get { return (double)GetValue(BorderWidthProperty); }
|
||||
set { SetValue(BorderWidthProperty, value); }
|
||||
}
|
||||
|
||||
public Color BorderColor
|
||||
{
|
||||
get { return (Color)GetValue(BorderColorProperty); }
|
||||
set { SetValue(BorderColorProperty, value); }
|
||||
}
|
||||
|
||||
public int CornerRadius
|
||||
{
|
||||
get { return (int)GetValue(CornerRadiusProperty); }
|
||||
set { SetValue(CornerRadiusProperty, value); }
|
||||
}
|
||||
|
||||
public RadioButton()
|
||||
{
|
||||
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<RadioButton>>(() => new PlatformConfigurationRegistry<RadioButton>(this));
|
||||
ExperimentalFlags.VerifyFlagEnabled(nameof(RadioButton), ExperimentalFlags.RadioButtonExperimental, nameof(RadioButton));
|
||||
|
||||
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<RadioButton>>(() =>
|
||||
new PlatformConfigurationRegistry<RadioButton>(this));
|
||||
}
|
||||
|
||||
|
||||
static bool isExperimentalFlagSet = false;
|
||||
internal static void VerifyExperimental([CallerMemberName] string memberName = "", string constructorHint = null)
|
||||
{
|
||||
if (isExperimentalFlagSet)
|
||||
return;
|
||||
|
||||
ExperimentalFlags.VerifyFlagEnabled(nameof(RadioButton), ExperimentalFlags.RadioButtonExperimental, constructorHint, memberName);
|
||||
|
||||
isExperimentalFlagSet = true;
|
||||
}
|
||||
|
||||
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
|
||||
{
|
||||
VerifyExperimental(nameof(RadioButton));
|
||||
return base.OnMeasure(widthConstraint, heightConstraint);
|
||||
}
|
||||
|
||||
public new IPlatformElementConfiguration<T, RadioButton> On<T>() where T : IConfigPlatform
|
||||
public IPlatformElementConfiguration<T, RadioButton> On<T>() where T : IConfigPlatform
|
||||
{
|
||||
return _platformConfigurationRegistry.Value.On<T>();
|
||||
}
|
||||
|
||||
public static ControlTemplate DefaultTemplate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_defaultTemplate == null)
|
||||
{
|
||||
s_defaultTemplate = new ControlTemplate(() => BuildDefaultTemplate());
|
||||
}
|
||||
|
||||
return s_defaultTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
void ITextElement.OnTextTransformChanged(TextTransform oldValue, TextTransform newValue)
|
||||
=> InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
|
||||
void ITextElement.OnTextColorPropertyChanged(Color oldValue, Color newValue)
|
||||
{
|
||||
}
|
||||
|
||||
void ITextElement.OnCharacterSpacingPropertyChanged(double oldValue, double newValue)
|
||||
=> InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
|
||||
void IFontElement.OnFontFamilyChanged(string oldValue, string newValue) =>
|
||||
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
|
||||
void IFontElement.OnFontSizeChanged(double oldValue, double newValue) =>
|
||||
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
|
||||
void IFontElement.OnFontAttributesChanged(FontAttributes oldValue, FontAttributes newValue) =>
|
||||
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
|
||||
void IFontElement.OnFontChanged(Font oldValue, Font newValue) =>
|
||||
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
|
||||
|
||||
double IFontElement.FontSizeDefaultValueCreator() =>
|
||||
Device.GetNamedSize(NamedSize.Default, this);
|
||||
|
||||
public virtual string UpdateFormsText(string source, TextTransform textTransform)
|
||||
=> TextTransformUtilites.GetTransformedText(source, textTransform);
|
||||
|
||||
int IBorderElement.CornerRadiusDefaultValue => (int)BorderElement.CornerRadiusProperty.DefaultValue;
|
||||
|
||||
Color IBorderElement.BorderColorDefaultValue => (Color)BorderElement.BorderColorProperty.DefaultValue;
|
||||
|
||||
double IBorderElement.BorderWidthDefaultValue => (double)BorderElement.BorderWidthProperty.DefaultValue;
|
||||
|
||||
void IBorderElement.OnBorderColorPropertyChanged(Color oldValue, Color newValue)
|
||||
{
|
||||
}
|
||||
|
||||
bool IBorderElement.IsCornerRadiusSet() => IsSet(BorderElement.CornerRadiusProperty);
|
||||
bool IBorderElement.IsBackgroundColorSet() => IsSet(BackgroundColorProperty);
|
||||
bool IBorderElement.IsBackgroundSet() => IsSet(BackgroundProperty);
|
||||
bool IBorderElement.IsBorderColorSet() => IsSet(BorderElement.BorderColorProperty);
|
||||
bool IBorderElement.IsBorderWidthSet() => IsSet(BorderElement.BorderWidthProperty);
|
||||
|
||||
protected internal override void ChangeVisualState()
|
||||
{
|
||||
if (IsEnabled && IsChecked)
|
||||
VisualStateManager.GoToState(this, IsCheckedVisualState);
|
||||
ApplyIsCheckedState();
|
||||
|
||||
base.ChangeVisualState();
|
||||
}
|
||||
|
||||
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
|
||||
{
|
||||
if (UsingRenderer)
|
||||
{
|
||||
return Device.PlatformServices.GetNativeSize(this, widthConstraint, heightConstraint);
|
||||
}
|
||||
|
||||
return base.OnMeasure(widthConstraint, heightConstraint);
|
||||
}
|
||||
|
||||
public override ControlTemplate ResolveControlTemplate()
|
||||
{
|
||||
var template = base.ResolveControlTemplate();
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
if (!RendererAvailable)
|
||||
{
|
||||
ControlTemplate = DefaultTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
return ControlTemplate;
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_templateRoot = (this as IControlTemplated)?.TemplateRoot as View;
|
||||
|
||||
ApplyIsCheckedState();
|
||||
UpdateIsEnabled();
|
||||
}
|
||||
|
||||
internal override void OnControlTemplateChanged(ControlTemplate oldValue, ControlTemplate newValue)
|
||||
{
|
||||
base.OnControlTemplateChanged(oldValue, newValue);
|
||||
}
|
||||
|
||||
bool UsingRenderer => ControlTemplate == null;
|
||||
|
||||
void UpdateIsEnabled()
|
||||
{
|
||||
if (UsingRenderer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tapGestureRecognizer == null)
|
||||
{
|
||||
_tapGestureRecognizer = new TapGestureRecognizer();
|
||||
}
|
||||
|
||||
if (IsEnabled)
|
||||
{
|
||||
_tapGestureRecognizer.Tapped += SelectRadioButton;
|
||||
GestureRecognizers.Add(_tapGestureRecognizer);
|
||||
}
|
||||
else
|
||||
base.ChangeVisualState();
|
||||
{
|
||||
_tapGestureRecognizer.Tapped -= SelectRadioButton;
|
||||
GestureRecognizers.Remove(_tapGestureRecognizer);
|
||||
}
|
||||
}
|
||||
|
||||
static bool RendererAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!s_rendererAvailable.HasValue)
|
||||
{
|
||||
s_rendererAvailable = Internals.Registrar.Registered.GetHandlerType(typeof(RadioButton)) != null;
|
||||
}
|
||||
|
||||
return s_rendererAvailable.Value;
|
||||
}
|
||||
}
|
||||
|
||||
static Brush ResolveThemeColor(string key)
|
||||
{
|
||||
if (Application.Current.TryGetResource(key, out object color))
|
||||
{
|
||||
return (Brush)color;
|
||||
}
|
||||
|
||||
if (Application.Current?.RequestedTheme == OSAppTheme.Dark)
|
||||
{
|
||||
return Brush.White;
|
||||
}
|
||||
|
||||
return Brush.Black;
|
||||
}
|
||||
|
||||
void ApplyIsCheckedState()
|
||||
{
|
||||
if (IsChecked)
|
||||
{
|
||||
VisualStateManager.GoToState(this, CheckedVisualState);
|
||||
if (_templateRoot != null)
|
||||
{
|
||||
VisualStateManager.GoToState(_templateRoot, CheckedVisualState);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, UncheckedVisualState);
|
||||
if (_templateRoot != null)
|
||||
{
|
||||
VisualStateManager.GoToState(_templateRoot, UncheckedVisualState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SelectRadioButton(object sender, EventArgs e)
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
IsChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
void OnIsCheckedPropertyChanged(bool isChecked)
|
||||
{
|
||||
if (isChecked)
|
||||
UpdateRadioButtonGroup();
|
||||
RadioButtonGroup.UpdateRadioButtonGroup(this);
|
||||
|
||||
CheckedChanged?.Invoke(this, new CheckedChangedEventArgs(isChecked));
|
||||
ChangeVisualState();
|
||||
CheckedChanged?.Invoke(this, new CheckedChangedEventArgs(isChecked));
|
||||
}
|
||||
|
||||
void OnValuePropertyChanged()
|
||||
{
|
||||
if (!IsChecked || string.IsNullOrEmpty(GroupName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MessagingCenter.Send(this, ValueChangedMessage,
|
||||
new RadioButtonValueChanged(RadioButtonGroup.GetVisualRoot(this)));
|
||||
}
|
||||
|
||||
void OnGroupNamePropertyChanged(string oldGroupName, string newGroupName)
|
||||
{
|
||||
// Unregister the old group name if set
|
||||
if (!string.IsNullOrEmpty(oldGroupName))
|
||||
Unregister(this, oldGroupName);
|
||||
|
||||
// Register the new group name is set
|
||||
if (!string.IsNullOrEmpty(newGroupName))
|
||||
Register(this, newGroupName);
|
||||
}
|
||||
|
||||
void UpdateRadioButtonGroup()
|
||||
{
|
||||
string groupName = GroupName;
|
||||
if (!string.IsNullOrEmpty(groupName))
|
||||
{
|
||||
Element rootScope = GetVisualRoot(this);
|
||||
|
||||
if (_groupNameToElements == null)
|
||||
_groupNameToElements = new Dictionary<string, List<WeakReference<RadioButton>>>(1);
|
||||
|
||||
// Get all elements bound to this key and remove this element
|
||||
List<WeakReference<RadioButton>> elements = _groupNameToElements[groupName];
|
||||
for (int i = 0; i < elements.Count;)
|
||||
if (string.IsNullOrEmpty(oldGroupName))
|
||||
{
|
||||
WeakReference<RadioButton> weakRef = elements[i];
|
||||
if (weakRef.TryGetTarget(out RadioButton rb))
|
||||
{
|
||||
// Uncheck all checked RadioButtons different from the current one
|
||||
if (rb != this && (rb.IsChecked == true) && rootScope == GetVisualRoot(rb))
|
||||
rb.SetValueFromRenderer(IsCheckedProperty, false);
|
||||
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove dead instances
|
||||
elements.RemoveAt(i);
|
||||
}
|
||||
MessagingCenter.Subscribe<RadioButton, RadioButtonGroupSelectionChanged>(this,
|
||||
RadioButtonGroup.GroupSelectionChangedMessage, HandleRadioButtonGroupSelectionChanged);
|
||||
MessagingCenter.Subscribe<Layout<View>, RadioButtonGroupValueChanged>(this,
|
||||
RadioButtonGroup.GroupValueChangedMessage, HandleRadioButtonGroupValueChanged);
|
||||
}
|
||||
}
|
||||
else // Logical parent should be the group
|
||||
{
|
||||
Element parent = Parent;
|
||||
if (parent != null)
|
||||
{
|
||||
// Traverse logical children
|
||||
IEnumerable children = parent.LogicalChildren;
|
||||
IEnumerator itor = children.GetEnumerator();
|
||||
while (itor.MoveNext())
|
||||
{
|
||||
var rb = itor.Current as RadioButton;
|
||||
if (rb != null && rb != this && string.IsNullOrEmpty(rb.GroupName) && (rb.IsChecked == true))
|
||||
rb.SetValueFromRenderer(IsCheckedProperty, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Register(RadioButton radioButton, string groupName)
|
||||
{
|
||||
if (_groupNameToElements == null)
|
||||
_groupNameToElements = new Dictionary<string, List<WeakReference<RadioButton>>>(1);
|
||||
|
||||
if (_groupNameToElements.TryGetValue(groupName, out List<WeakReference<RadioButton>> elements))
|
||||
{
|
||||
// There were some elements there, remove dead ones
|
||||
PurgeDead(elements, null);
|
||||
MessagingCenter.Send(this, GroupNameChangedMessage,
|
||||
new RadioButtonGroupNameChanged(RadioButtonGroup.GetVisualRoot(this), oldGroupName));
|
||||
}
|
||||
else
|
||||
{
|
||||
elements = new List<WeakReference<RadioButton>>(1);
|
||||
_groupNameToElements[groupName] = elements;
|
||||
if (!string.IsNullOrEmpty(oldGroupName))
|
||||
{
|
||||
MessagingCenter.Unsubscribe<RadioButton, RadioButtonGroupSelectionChanged>(this, RadioButtonGroup.GroupSelectionChangedMessage);
|
||||
MessagingCenter.Unsubscribe<Layout<View>, RadioButtonGroupValueChanged>(this, RadioButtonGroup.GroupValueChangedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
elements.Add(new WeakReference<RadioButton>(radioButton));
|
||||
}
|
||||
|
||||
static void Unregister(RadioButton radioButton, string groupName)
|
||||
bool MatchesScope(RadioButtonScopeMessage message)
|
||||
{
|
||||
if (_groupNameToElements == null)
|
||||
return RadioButtonGroup.GetVisualRoot(this) == message.Scope;
|
||||
}
|
||||
|
||||
void HandleRadioButtonGroupSelectionChanged(RadioButton selected, RadioButtonGroupSelectionChanged args)
|
||||
{
|
||||
if (!IsChecked || selected == this || string.IsNullOrEmpty(GroupName) || GroupName != selected.GroupName || !MatchesScope(args))
|
||||
{
|
||||
return;
|
||||
|
||||
// Get all elements bound to this key and remove this element
|
||||
if (_groupNameToElements.TryGetValue(groupName, out List<WeakReference<RadioButton>> elements))
|
||||
{
|
||||
PurgeDead(elements, radioButton);
|
||||
|
||||
if (elements.Count == 0)
|
||||
_groupNameToElements.Remove(groupName);
|
||||
}
|
||||
|
||||
IsChecked = false;
|
||||
}
|
||||
|
||||
static void PurgeDead(List<WeakReference<RadioButton>> elements, object elementToRemove)
|
||||
void HandleRadioButtonGroupValueChanged(Layout<View> layout, RadioButtonGroupValueChanged args)
|
||||
{
|
||||
for (int i = 0; i < elements.Count;)
|
||||
if (IsChecked || string.IsNullOrEmpty(GroupName) || GroupName != args.GroupName || Value != args.Value || !MatchesScope(args))
|
||||
{
|
||||
if (elements[i].TryGetTarget(out RadioButton rb) && rb == elementToRemove)
|
||||
elements.RemoveAt(i);
|
||||
else
|
||||
i++;
|
||||
return;
|
||||
}
|
||||
|
||||
IsChecked = true;
|
||||
}
|
||||
|
||||
static Element GetVisualRoot(Element element)
|
||||
static View BuildDefaultTemplate()
|
||||
{
|
||||
Element parent = element.Parent;
|
||||
while (parent != null && !(parent is Page))
|
||||
parent = parent.Parent;
|
||||
return parent;
|
||||
var frame = new Frame
|
||||
{
|
||||
HasShadow = false,
|
||||
BackgroundColor = Color.Transparent,
|
||||
VerticalOptions = LayoutOptions.Start,
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
Margin = new Thickness(6),
|
||||
Padding = new Thickness(0)
|
||||
};
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
RowSpacing = 0,
|
||||
ColumnDefinitions = new ColumnDefinitionCollection {
|
||||
new ColumnDefinition { Width = GridLength.Auto },
|
||||
new ColumnDefinition { Width = GridLength.Star }
|
||||
},
|
||||
RowDefinitions = new RowDefinitionCollection {
|
||||
new RowDefinition { Height = GridLength.Auto }
|
||||
}
|
||||
};
|
||||
|
||||
var normalEllipse = new Ellipse
|
||||
{
|
||||
Fill = Brush.Transparent,
|
||||
Aspect = Stretch.Uniform,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
HeightRequest = 21,
|
||||
WidthRequest = 21,
|
||||
StrokeThickness = 2,
|
||||
Stroke = RadioButtonThemeColor,
|
||||
InputTransparent = true
|
||||
};
|
||||
|
||||
var checkMark = new Ellipse
|
||||
{
|
||||
Fill = RadioButtonCheckMarkThemeColor,
|
||||
Aspect = Stretch.Uniform,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
HeightRequest = 11,
|
||||
WidthRequest = 11,
|
||||
Opacity = 0,
|
||||
InputTransparent = true
|
||||
};
|
||||
|
||||
var contentPresenter = new ContentPresenter
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
};
|
||||
|
||||
contentPresenter.SetBinding(MarginProperty, new Binding("Padding", source: RelativeBindingSource.TemplatedParent));
|
||||
contentPresenter.SetBinding(BackgroundColorProperty, new Binding(BackgroundColorProperty.PropertyName,
|
||||
source: RelativeBindingSource.TemplatedParent));
|
||||
|
||||
grid.Children.Add(normalEllipse);
|
||||
grid.Children.Add(checkMark);
|
||||
grid.Children.Add(contentPresenter, 1, 0);
|
||||
|
||||
frame.Content = grid;
|
||||
|
||||
INameScope nameScope = new NameScope();
|
||||
NameScope.SetNameScope(frame, nameScope);
|
||||
nameScope.RegisterName(TemplateRootName, frame);
|
||||
nameScope.RegisterName(UncheckedButton, normalEllipse);
|
||||
nameScope.RegisterName(CheckedIndicator, checkMark);
|
||||
nameScope.RegisterName("ContentPresenter", contentPresenter);
|
||||
|
||||
VisualStateGroupList visualStateGroups = new VisualStateGroupList();
|
||||
|
||||
var common = new VisualStateGroup() { Name = "Common" };
|
||||
common.States.Add(new VisualState() { Name = VisualStateManager.CommonStates.Normal });
|
||||
common.States.Add(new VisualState() { Name = VisualStateManager.CommonStates.Disabled });
|
||||
|
||||
visualStateGroups.Add(common);
|
||||
|
||||
var checkedStates = new VisualStateGroup() { Name = "CheckedStates" };
|
||||
|
||||
VisualState checkedVisualState = new VisualState() { Name = CheckedVisualState };
|
||||
checkedVisualState.Setters.Add(new Setter() { Property = OpacityProperty, TargetName = CheckedIndicator, Value = 1 });
|
||||
checkedVisualState.Setters.Add(new Setter() { Property = Shape.StrokeProperty, TargetName = UncheckedButton, Value = RadioButtonCheckMarkThemeColor });
|
||||
checkedStates.States.Add(checkedVisualState);
|
||||
|
||||
VisualState uncheckedVisualState = new VisualState() { Name = UncheckedVisualState };
|
||||
uncheckedVisualState.Setters.Add(new Setter() { Property = OpacityProperty, TargetName = CheckedIndicator, Value = 0 });
|
||||
uncheckedVisualState.Setters.Add(new Setter() { Property = Shape.StrokeProperty, TargetName = UncheckedButton, Value = RadioButtonThemeColor });
|
||||
checkedStates.States.Add(uncheckedVisualState);
|
||||
|
||||
visualStateGroups.Add(checkedStates);
|
||||
|
||||
VisualStateManager.SetVisualStateGroups(frame, visualStateGroups);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public string ContentAsString()
|
||||
{
|
||||
var content = Content;
|
||||
if (content is View)
|
||||
{
|
||||
Log.Warning("RadioButton", $"Warning - {Device.RuntimePlatform} does not support {nameof(View)} as the {ContentProperty.PropertyName} property of {nameof(RadioButton)}; the return value of the ToString() method will be displayed instead.");
|
||||
}
|
||||
|
||||
return content?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
public static class RadioButtonGroup
|
||||
{
|
||||
internal const string GroupSelectionChangedMessage = "RadioButtonGroupSelectionChanged";
|
||||
internal const string GroupValueChangedMessage = "RadioButtonGroupValueChanged";
|
||||
|
||||
static readonly BindableProperty RadioButtonGroupControllerProperty =
|
||||
BindableProperty.CreateAttached("RadioButtonGroupController", typeof(RadioButtonGroupController), typeof(Layout<View>), default(RadioButtonGroupController),
|
||||
defaultValueCreator: (b) => new RadioButtonGroupController((Layout<View>)b),
|
||||
propertyChanged: (b, o, n) => OnControllerChanged(b, (RadioButtonGroupController)o, (RadioButtonGroupController)n));
|
||||
|
||||
static RadioButtonGroupController GetRadioButtonGroupController(BindableObject b)
|
||||
{
|
||||
return (RadioButtonGroupController)b.GetValue(RadioButtonGroupControllerProperty);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty GroupNameProperty =
|
||||
BindableProperty.Create("GroupName", typeof(string), typeof(Layout<View>), null,
|
||||
propertyChanged: (b, o, n) => { GetRadioButtonGroupController(b).GroupName = (string)n; });
|
||||
|
||||
public static string GetGroupName(BindableObject b)
|
||||
{
|
||||
return (string)b.GetValue(GroupNameProperty);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty SelectedValueProperty =
|
||||
BindableProperty.Create("SelectedValue", typeof(object), typeof(Layout<View>), null,
|
||||
defaultBindingMode: BindingMode.TwoWay,
|
||||
propertyChanged: (b, o, n) => { GetRadioButtonGroupController(b).SelectedValue = n; });
|
||||
|
||||
public static object GetSelectedValue(BindableObject bindableObject)
|
||||
{
|
||||
return bindableObject.GetValue(SelectedValueProperty);
|
||||
}
|
||||
|
||||
internal static void UpdateRadioButtonGroup(RadioButton radioButton)
|
||||
{
|
||||
string groupName = radioButton.GroupName;
|
||||
|
||||
Element scope = string.IsNullOrEmpty(groupName)
|
||||
? GroupByParent(radioButton)
|
||||
: GetVisualRoot(radioButton);
|
||||
|
||||
MessagingCenter.Send(radioButton, GroupSelectionChangedMessage,
|
||||
new RadioButtonGroupSelectionChanged(scope));
|
||||
}
|
||||
|
||||
internal static Element GroupByParent(RadioButton radioButton)
|
||||
{
|
||||
Element parent = radioButton.Parent;
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
// Traverse logical children
|
||||
IEnumerable children = parent.LogicalChildren;
|
||||
IEnumerator itor = children.GetEnumerator();
|
||||
while (itor.MoveNext())
|
||||
{
|
||||
var rb = itor.Current as RadioButton;
|
||||
if (rb != null && rb != radioButton && string.IsNullOrEmpty(rb.GroupName) && (rb.IsChecked == true))
|
||||
rb.SetValueFromRenderer(RadioButton.IsCheckedProperty, false);
|
||||
}
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
static void OnControllerChanged(BindableObject bindableObject, RadioButtonGroupController oldController,
|
||||
RadioButtonGroupController newController)
|
||||
{
|
||||
if (newController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
newController.GroupName = GetGroupName(bindableObject);
|
||||
newController.SelectedValue = GetSelectedValue(bindableObject);
|
||||
}
|
||||
|
||||
internal static Element GetVisualRoot(Element element)
|
||||
{
|
||||
Element parent = element.Parent;
|
||||
while (parent != null && !(parent is Page))
|
||||
parent = parent.Parent;
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
using System;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
internal class RadioButtonGroupController
|
||||
{
|
||||
readonly Layout<View> _layout;
|
||||
|
||||
string _groupName;
|
||||
private object _selectedValue;
|
||||
|
||||
public string GroupName { get => _groupName; set => SetGroupName(value); }
|
||||
public object SelectedValue { get => _selectedValue; set => SetSelectedValue(value); }
|
||||
|
||||
public RadioButtonGroupController(Layout<View> layout)
|
||||
{
|
||||
if (layout is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(layout));
|
||||
}
|
||||
|
||||
_layout = layout;
|
||||
layout.ChildAdded += ChildAdded;
|
||||
|
||||
if (!string.IsNullOrEmpty(_groupName))
|
||||
{
|
||||
UpdateGroupNames(layout, _groupName);
|
||||
}
|
||||
|
||||
MessagingCenter.Subscribe<RadioButton, RadioButtonGroupSelectionChanged>(this,
|
||||
RadioButtonGroup.GroupSelectionChangedMessage, HandleRadioButtonGroupSelectionChanged);
|
||||
MessagingCenter.Subscribe<RadioButton, RadioButtonGroupNameChanged>(this, RadioButton.GroupNameChangedMessage,
|
||||
HandleRadioButtonGroupNameChanged);
|
||||
MessagingCenter.Subscribe<RadioButton, RadioButtonValueChanged>(this, RadioButton.ValueChangedMessage,
|
||||
HandleRadioButtonValueChanged);
|
||||
}
|
||||
|
||||
bool MatchesScope(RadioButtonScopeMessage message)
|
||||
{
|
||||
return RadioButtonGroup.GetVisualRoot(_layout) == message.Scope;
|
||||
}
|
||||
|
||||
void HandleRadioButtonGroupSelectionChanged(RadioButton selected, RadioButtonGroupSelectionChanged args)
|
||||
{
|
||||
if (selected.GroupName != _groupName || !MatchesScope(args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_layout.SetValue(RadioButtonGroup.SelectedValueProperty, selected.Value);
|
||||
}
|
||||
|
||||
void HandleRadioButtonGroupNameChanged(RadioButton radioButton, RadioButtonGroupNameChanged args)
|
||||
{
|
||||
if (args.OldName != _groupName || !MatchesScope(args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_layout.ClearValue(RadioButtonGroup.SelectedValueProperty);
|
||||
}
|
||||
|
||||
void HandleRadioButtonValueChanged(RadioButton radioButton, RadioButtonValueChanged args)
|
||||
{
|
||||
if (radioButton.GroupName != _groupName || !MatchesScope(args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_layout.SetValue(RadioButtonGroup.SelectedValueProperty, radioButton.Value);
|
||||
}
|
||||
|
||||
void ChildAdded(object sender, ElementEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_groupName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Element is RadioButton radioButton)
|
||||
{
|
||||
AddRadioButton(radioButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var element in e.Element.Descendants())
|
||||
{
|
||||
if (element is RadioButton radioButton1)
|
||||
{
|
||||
AddRadioButton(radioButton1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddRadioButton(RadioButton radioButton)
|
||||
{
|
||||
UpdateGroupName(radioButton, _groupName);
|
||||
|
||||
if (radioButton.IsChecked)
|
||||
{
|
||||
_layout.SetValue(RadioButtonGroup.SelectedValueProperty, radioButton.Value);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateGroupName(Element element, string name, string oldName = null)
|
||||
{
|
||||
if (!(element is RadioButton radioButton))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentName = radioButton.GroupName;
|
||||
|
||||
if (string.IsNullOrEmpty(currentName) || currentName == oldName)
|
||||
{
|
||||
radioButton.GroupName = name;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateGroupNames(Layout<View> layout, string name, string oldName = null)
|
||||
{
|
||||
foreach (var descendant in layout.Descendants())
|
||||
{
|
||||
UpdateGroupName(descendant, name, oldName);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSelectedValue(object radioButtonValue)
|
||||
{
|
||||
_selectedValue = radioButtonValue;
|
||||
|
||||
if (radioButtonValue != null)
|
||||
{
|
||||
MessagingCenter.Send(_layout, RadioButtonGroup.GroupValueChangedMessage,
|
||||
new RadioButtonGroupValueChanged(_groupName, RadioButtonGroup.GetVisualRoot(_layout), radioButtonValue));
|
||||
}
|
||||
}
|
||||
|
||||
void SetGroupName(string groupName)
|
||||
{
|
||||
var oldName = _groupName;
|
||||
_groupName = groupName;
|
||||
UpdateGroupNames(_layout, _groupName, oldName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
namespace Xamarin.Forms
|
||||
{
|
||||
internal abstract class RadioButtonScopeMessage
|
||||
{
|
||||
public Element Scope { get; }
|
||||
|
||||
protected RadioButtonScopeMessage(Element scope) => Scope = scope;
|
||||
}
|
||||
|
||||
internal class RadioButtonGroupSelectionChanged : RadioButtonScopeMessage
|
||||
{
|
||||
public RadioButtonGroupSelectionChanged(Element scope) : base(scope) { }
|
||||
}
|
||||
|
||||
internal class RadioButtonGroupNameChanged : RadioButtonScopeMessage
|
||||
{
|
||||
public string OldName { get; }
|
||||
|
||||
public RadioButtonGroupNameChanged(Element scope, string oldName) : base(scope)
|
||||
{
|
||||
OldName = oldName;
|
||||
}
|
||||
}
|
||||
|
||||
internal class RadioButtonValueChanged : RadioButtonScopeMessage
|
||||
{
|
||||
public RadioButtonValueChanged(Element scope) : base(scope) { }
|
||||
}
|
||||
|
||||
internal class RadioButtonGroupValueChanged : RadioButtonScopeMessage
|
||||
{
|
||||
public object Value { get; }
|
||||
public string GroupName { get; }
|
||||
|
||||
public RadioButtonGroupValueChanged(string groupName, Element scope, object value) : base(scope)
|
||||
{
|
||||
GroupName = groupName;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
|
@ -8,25 +9,74 @@ namespace Xamarin.Forms
|
|||
{
|
||||
static int s_routeCount = 0;
|
||||
static Dictionary<string, RouteFactory> s_routes = new Dictionary<string, RouteFactory>();
|
||||
static Dictionary<string, Page> s_implicitPageRoutes = new Dictionary<string, Page>();
|
||||
|
||||
const string ImplicitPrefix = "IMPL_";
|
||||
const string DefaultPrefix = "D_FAULT_";
|
||||
const string _pathSeparator = "/";
|
||||
internal const string PathSeparator = "/";
|
||||
|
||||
// We only need these while a navigation is happening
|
||||
internal static void ClearImplicitPageRoutes()
|
||||
{
|
||||
s_implicitPageRoutes.Clear();
|
||||
}
|
||||
|
||||
internal static void RegisterImplicitPageRoute(Page page)
|
||||
{
|
||||
var route = GetRoute(page);
|
||||
if (!IsUserDefined(route))
|
||||
s_implicitPageRoutes[route] = page;
|
||||
}
|
||||
|
||||
// Shell works much better if the entire nav stack can be represented by a string
|
||||
// If the users pushes pages without using routes we want these page keys tracked
|
||||
internal static void RegisterImplicitPageRoutes(Shell shell)
|
||||
{
|
||||
foreach (var item in shell.Items)
|
||||
{
|
||||
foreach (var section in item.Items)
|
||||
{
|
||||
var navigationStackCount = section.Navigation.NavigationStack.Count;
|
||||
for (int i = 1; i < navigationStackCount; i++)
|
||||
{
|
||||
RegisterImplicitPageRoute(section.Navigation.NavigationStack[i]);
|
||||
}
|
||||
var navigationModalStackCount = section.Navigation.ModalStack.Count;
|
||||
for (int i = 0; i < navigationModalStackCount; i++)
|
||||
{
|
||||
var page = section.Navigation.ModalStack[i];
|
||||
RegisterImplicitPageRoute(page);
|
||||
|
||||
if (page is NavigationPage np)
|
||||
{
|
||||
foreach (var npPages in np.Pages)
|
||||
{
|
||||
RegisterImplicitPageRoute(npPages);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GenerateImplicitRoute(string source)
|
||||
{
|
||||
if (IsImplicit(source))
|
||||
return source;
|
||||
|
||||
return String.Concat(ImplicitPrefix, source);
|
||||
}
|
||||
|
||||
internal static bool IsImplicit(string source)
|
||||
{
|
||||
return source.StartsWith(ImplicitPrefix, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
internal static bool IsImplicit(BindableObject source)
|
||||
{
|
||||
return IsImplicit(GetRoute(source));
|
||||
}
|
||||
|
||||
internal static bool IsDefault(string source)
|
||||
{
|
||||
return source.StartsWith(DefaultPrefix, StringComparison.Ordinal);
|
||||
|
@ -42,7 +92,15 @@ namespace Xamarin.Forms
|
|||
if (source == null)
|
||||
return false;
|
||||
|
||||
return !(IsDefault(source) || IsImplicit(source));
|
||||
return IsUserDefined(GetRoute(source));
|
||||
}
|
||||
|
||||
internal static bool IsUserDefined(string route)
|
||||
{
|
||||
if (route == null)
|
||||
return false;
|
||||
|
||||
return !(IsDefault(route) || IsImplicit(route));
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
|
@ -61,8 +119,9 @@ namespace Xamarin.Forms
|
|||
|
||||
internal static string[] GetRouteKeys()
|
||||
{
|
||||
string[] keys = new string[s_routes.Count];
|
||||
string[] keys = new string[s_routes.Count + s_implicitPageRoutes.Count];
|
||||
s_routes.Keys.CopyTo(keys, 0);
|
||||
s_implicitPageRoutes.Keys.CopyTo(keys, s_routes.Count);
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
@ -70,6 +129,11 @@ namespace Xamarin.Forms
|
|||
{
|
||||
Element result = null;
|
||||
|
||||
if (s_implicitPageRoutes.TryGetValue(route, out var page))
|
||||
{
|
||||
return page;
|
||||
}
|
||||
|
||||
if (s_routes.TryGetValue(route, out var content))
|
||||
result = content.GetOrCreate();
|
||||
|
||||
|
@ -101,43 +165,9 @@ namespace Xamarin.Forms
|
|||
return $"{source}/";
|
||||
}
|
||||
|
||||
internal static Uri Remove(Uri uri, bool implicitRoutes, bool defaultRoutes)
|
||||
{
|
||||
uri = ShellUriHandler.FormatUri(uri, null);
|
||||
|
||||
string[] parts = uri.OriginalString.TrimEnd(_pathSeparator[0]).Split(_pathSeparator[0]);
|
||||
|
||||
bool userDefinedRouteAdded = false;
|
||||
List<string> toKeep = new List<string>();
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
// This means there are no routes defined on the shell but the user has navigated to a global route
|
||||
// so we need to attach the final route where the user left the shell
|
||||
if (s_routes.ContainsKey(parts[i]) && !userDefinedRouteAdded && i > 0)
|
||||
{
|
||||
toKeep.Add(parts[i - 1]);
|
||||
}
|
||||
|
||||
if (!(IsDefault(parts[i]) && defaultRoutes) && !(IsImplicit(parts[i]) && implicitRoutes))
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(parts[i]))
|
||||
userDefinedRouteAdded = true;
|
||||
|
||||
toKeep.Add(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!userDefinedRouteAdded && parts.Length > 0)
|
||||
{
|
||||
toKeep.Add(parts[parts.Length - 1]);
|
||||
}
|
||||
|
||||
return new Uri(string.Join(_pathSeparator, toKeep), UriKind.Relative);
|
||||
}
|
||||
|
||||
public static string FormatRoute(List<string> segments)
|
||||
{
|
||||
var route = FormatRoute(String.Join(_pathSeparator, segments));
|
||||
var route = FormatRoute(String.Join(PathSeparator, segments));
|
||||
return route;
|
||||
}
|
||||
|
||||
|
@ -188,7 +218,7 @@ namespace Xamarin.Forms
|
|||
}
|
||||
|
||||
RouteFactory existingRegistration = null;
|
||||
if(s_routes.TryGetValue(route, out existingRegistration) && !existingRegistration.Equals(routeFactory))
|
||||
if (s_routes.TryGetValue(route, out existingRegistration) && !existingRegistration.Equals(routeFactory))
|
||||
throw new ArgumentException($"Duplicated Route: \"{route}\"");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,18 @@
|
|||
{
|
||||
public class EllipseGeometry : Geometry
|
||||
{
|
||||
public EllipseGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public EllipseGeometry(Point center, double radiusX, double radiusY)
|
||||
{
|
||||
Center = center;
|
||||
RadiusX = radiusX;
|
||||
RadiusY = radiusY;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty CenterProperty =
|
||||
BindableProperty.Create(nameof(Center), typeof(Point), typeof(EllipseGeometry), new Point());
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
namespace Xamarin.Forms.Shapes
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Xamarin.Forms.Shapes
|
||||
{
|
||||
[ContentProperty("Children")]
|
||||
public sealed class GeometryGroup : Geometry
|
||||
public class GeometryGroup : Geometry
|
||||
{
|
||||
public static readonly BindableProperty ChildrenProperty =
|
||||
BindableProperty.Create(nameof(Children), typeof(GeometryCollection), typeof(GeometryGroup), null);
|
||||
BindableProperty.Create(nameof(Children), typeof(GeometryCollection), typeof(GeometryGroup), null,
|
||||
propertyChanged: OnChildrenChanged);
|
||||
|
||||
static void OnChildrenChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as GeometryGroup)?.UpdateChildren(oldValue as GeometryCollection, newValue as GeometryCollection);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty FillRuleProperty =
|
||||
BindableProperty.Create(nameof(FillRule), typeof(FillRule), typeof(GeometryGroup), FillRule.EvenOdd);
|
||||
|
@ -25,5 +35,67 @@
|
|||
set { SetValue(FillRuleProperty, value); }
|
||||
get { return (FillRule)GetValue(FillRuleProperty); }
|
||||
}
|
||||
|
||||
public event EventHandler InvalidateGeometryRequested;
|
||||
|
||||
void UpdateChildren(GeometryCollection oldCollection, GeometryCollection newCollection)
|
||||
{
|
||||
if (oldCollection != null)
|
||||
{
|
||||
oldCollection.CollectionChanged -= OnChildrenCollectionChanged;
|
||||
|
||||
foreach (var oldChildren in oldCollection)
|
||||
{
|
||||
oldChildren.PropertyChanged -= OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (newCollection == null)
|
||||
return;
|
||||
|
||||
newCollection.CollectionChanged += OnChildrenCollectionChanged;
|
||||
|
||||
foreach (var newChildren in newCollection)
|
||||
{
|
||||
newChildren.PropertyChanged += OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (var oldItem in e.OldItems)
|
||||
{
|
||||
if (!(oldItem is Geometry oldGeometry))
|
||||
continue;
|
||||
|
||||
oldGeometry.PropertyChanged -= OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (var newItem in e.NewItems)
|
||||
{
|
||||
if (!(newItem is Geometry newGeometry))
|
||||
continue;
|
||||
|
||||
newGeometry.PropertyChanged += OnChildrenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void OnChildrenPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void Invalidate()
|
||||
{
|
||||
InvalidateGeometryRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,17 @@
|
|||
{
|
||||
public class LineGeometry : Geometry
|
||||
{
|
||||
public LineGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LineGeometry(Point startPoint, Point endPoint)
|
||||
{
|
||||
StartPoint = startPoint;
|
||||
EndPoint = endPoint;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty StartPointProperty =
|
||||
BindableProperty.Create(nameof(StartPoint), typeof(Point), typeof(LineGeometry), new Point());
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace Xamarin.Forms.Shapes
|
||||
namespace Xamarin.Forms.Shapes
|
||||
{
|
||||
[ContentProperty("Figures")]
|
||||
public sealed class PathGeometry : Geometry
|
||||
|
@ -10,6 +8,17 @@ namespace Xamarin.Forms.Shapes
|
|||
Figures = new PathFigureCollection();
|
||||
}
|
||||
|
||||
public PathGeometry(PathFigureCollection figures)
|
||||
{
|
||||
Figures = figures;
|
||||
}
|
||||
|
||||
public PathGeometry(PathFigureCollection figures, FillRule fillRule)
|
||||
{
|
||||
Figures = figures;
|
||||
FillRule = fillRule;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty FiguresProperty =
|
||||
BindableProperty.Create(nameof(Figures), typeof(PathFigureCollection), typeof(PathGeometry), null);
|
||||
|
||||
|
|
|
@ -2,6 +2,16 @@
|
|||
{
|
||||
public class RectangleGeometry : Geometry
|
||||
{
|
||||
public RectangleGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RectangleGeometry(Rect rect)
|
||||
{
|
||||
Rect = rect;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty RectProperty =
|
||||
BindableProperty.Create(nameof(Rect), typeof(Rect), typeof(RectangleGeometry), new Rect());
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
namespace Xamarin.Forms.Shapes
|
||||
{
|
||||
public class RoundRectangleGeometry : GeometryGroup
|
||||
{
|
||||
public RoundRectangleGeometry()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RoundRectangleGeometry(CornerRadius cornerRadius, Rect rect)
|
||||
{
|
||||
CornerRadius = cornerRadius;
|
||||
Rect = rect;
|
||||
}
|
||||
|
||||
public static readonly BindableProperty RectProperty =
|
||||
BindableProperty.Create(nameof(Rect), typeof(Rect), typeof(RoundRectangleGeometry), new Rect(),
|
||||
propertyChanged: OnRectChanged);
|
||||
|
||||
static void OnRectChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as RoundRectangleGeometry)?.UpdateGeometry();
|
||||
}
|
||||
|
||||
public Rect Rect
|
||||
{
|
||||
set { SetValue(RectProperty, value); }
|
||||
get { return (Rect)GetValue(RectProperty); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty CornerRadiusProperty =
|
||||
BindableProperty.Create(nameof(CornerRadius), typeof(CornerRadius), typeof(RoundRectangleGeometry), new CornerRadius(),
|
||||
propertyChanged: OnCornerRadiusChanged);
|
||||
|
||||
static void OnCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as RoundRectangleGeometry)?.UpdateGeometry();
|
||||
}
|
||||
|
||||
public CornerRadius CornerRadius
|
||||
{
|
||||
set { SetValue(CornerRadiusProperty, value); }
|
||||
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
|
||||
}
|
||||
|
||||
void UpdateGeometry()
|
||||
{
|
||||
FillRule = FillRule.Nonzero;
|
||||
|
||||
Children.Clear();
|
||||
|
||||
Children.Add(GetRoundRectangleGeometry());
|
||||
}
|
||||
|
||||
Geometry GetRoundRectangleGeometry()
|
||||
{
|
||||
GeometryGroup roundedRectGeometry = new GeometryGroup
|
||||
{
|
||||
FillRule = FillRule.Nonzero
|
||||
};
|
||||
|
||||
if (CornerRadius.TopLeft > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + CornerRadius.TopLeft, Rect.Location.Y + CornerRadius.TopLeft), Rect.Location.Y + CornerRadius.TopLeft, Rect.Location.Y + CornerRadius.TopLeft));
|
||||
|
||||
if (CornerRadius.TopRight > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + Rect.Width - CornerRadius.TopRight, Rect.Location.Y + CornerRadius.TopRight), CornerRadius.TopRight, CornerRadius.TopRight));
|
||||
|
||||
if (CornerRadius.BottomRight > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + Rect.Width - CornerRadius.BottomRight, Rect.Location.Y + Rect.Height - CornerRadius.BottomRight), CornerRadius.BottomRight, CornerRadius.BottomRight));
|
||||
|
||||
if (CornerRadius.BottomLeft > 0)
|
||||
roundedRectGeometry.Children.Add(
|
||||
new EllipseGeometry(new Point(Rect.Location.X + CornerRadius.BottomLeft, Rect.Location.Y + Rect.Height - CornerRadius.BottomLeft), CornerRadius.BottomLeft, CornerRadius.BottomLeft));
|
||||
|
||||
PathFigure pathFigure = new PathFigure
|
||||
{
|
||||
IsClosed = true,
|
||||
StartPoint = new Point(Rect.Location.X + CornerRadius.TopLeft, Rect.Location.Y),
|
||||
Segments = new PathSegmentCollection
|
||||
{
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width - CornerRadius.TopRight, Rect.Location.Y) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width, Rect.Location.Y + CornerRadius.TopRight) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width, Rect.Location.Y + Rect.Height - CornerRadius.BottomRight) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + Rect.Width - CornerRadius.BottomRight, Rect.Location.Y + Rect.Height) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X + CornerRadius.BottomLeft, Rect.Location.Y + Rect.Height) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X, Rect.Location.Y + Rect.Height - CornerRadius.BottomLeft) },
|
||||
new LineSegment { Point = new Point(Rect.Location.X, Rect.Location.Y + CornerRadius.TopLeft) }
|
||||
}
|
||||
};
|
||||
|
||||
PathFigureCollection pathFigureCollection = new PathFigureCollection
|
||||
{
|
||||
pathFigure
|
||||
};
|
||||
|
||||
roundedRectGeometry.Children.Add(new PathGeometry(pathFigureCollection, FillRule.Nonzero));
|
||||
|
||||
return roundedRectGeometry;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -209,6 +209,7 @@ namespace Xamarin.Forms
|
|||
|
||||
List<(IAppearanceObserver Observer, Element Pivot)> _appearanceObservers = new List<(IAppearanceObserver Observer, Element Pivot)>();
|
||||
List<IFlyoutBehaviorObserver> _flyoutBehaviorObservers = new List<IFlyoutBehaviorObserver>();
|
||||
ShellNavigatingEventArgs _deferredEventArgs;
|
||||
|
||||
|
||||
internal static BindableObject GetBindableObjectWithFlyoutItemTemplate(BindableObject bo)
|
||||
|
@ -403,7 +404,7 @@ namespace Xamarin.Forms
|
|||
bool IShellController.ProposeNavigation(ShellNavigationSource source, ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> stack, bool canCancel)
|
||||
{
|
||||
var proposedState = GetNavigationState(shellItem, shellSection, shellContent, stack, shellSection.Navigation.ModalStack);
|
||||
return ProposeNavigation(source, proposedState, canCancel);
|
||||
return ProposeNavigation(source, proposedState, canCancel, null);
|
||||
}
|
||||
|
||||
bool IShellController.RemoveAppearanceObserver(IAppearanceObserver observer)
|
||||
|
@ -434,7 +435,7 @@ namespace Xamarin.Forms
|
|||
|
||||
SetValueFromRenderer(CurrentStatePropertyKey, result);
|
||||
|
||||
ProcessNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
|
||||
HandleNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
|
||||
}
|
||||
ReadOnlyCollection<ShellItem> IShellController.GetItems() =>
|
||||
new ReadOnlyCollection<ShellItem>(((ShellItemCollection)Items).VisibleItemsReadOnly.ToList());
|
||||
|
@ -477,6 +478,11 @@ namespace Xamarin.Forms
|
|||
return routes;
|
||||
}
|
||||
|
||||
internal Task CompleteDeferredNavigating(ShellNavigatingEventArgs deferredArgs)
|
||||
{
|
||||
return GoToAsync(deferredArgs.Target, deferredArgs.Animate, false, deferredArgs);
|
||||
}
|
||||
|
||||
public Task GoToAsync(ShellNavigationState state)
|
||||
{
|
||||
return GoToAsync(state, null, false);
|
||||
|
@ -487,16 +493,52 @@ namespace Xamarin.Forms
|
|||
return GoToAsync(state, animate, false);
|
||||
}
|
||||
|
||||
internal async Task GoToAsync(ShellNavigationState state, bool? animate, bool enableRelativeShellRoutes)
|
||||
internal Task GoToAsync(ShellNavigationState state, bool? animate, bool enableRelativeShellRoutes, ShellNavigatingEventArgs deferredArgs = null)
|
||||
{
|
||||
return GoToAsync(new ShellNavigationParameters
|
||||
{
|
||||
TargetState = state,
|
||||
Animated = animate,
|
||||
EnableRelativeShellRoutes = enableRelativeShellRoutes,
|
||||
DeferredArgs = deferredArgs
|
||||
});
|
||||
}
|
||||
|
||||
internal async Task GoToAsync(ShellNavigationParameters shellNavigationParameters)
|
||||
{
|
||||
if (shellNavigationParameters.PagePushing != null)
|
||||
Routing.RegisterImplicitPageRoute(shellNavigationParameters.PagePushing);
|
||||
|
||||
ShellNavigationState state = shellNavigationParameters.TargetState ?? Routing.GetRoute(shellNavigationParameters.PagePushing);
|
||||
bool? animate = shellNavigationParameters.Animated;
|
||||
bool enableRelativeShellRoutes = shellNavigationParameters.EnableRelativeShellRoutes;
|
||||
ShellNavigatingEventArgs deferredArgs = shellNavigationParameters.DeferredArgs;
|
||||
|
||||
if (_deferredEventArgs != null && _deferredEventArgs != deferredArgs)
|
||||
{
|
||||
throw new InvalidOperationException("Not all ShellNavigatingDeferrals have been completed from the previous operation");
|
||||
}
|
||||
|
||||
// FIXME: This should not be none, we need to compute the delta and set flags correctly
|
||||
var accept = ProposeNavigation(ShellNavigationSource.Unknown, state, this.CurrentState != null);
|
||||
if (!accept)
|
||||
var accept = ProposeNavigation(ShellNavigationSource.Unknown, state, this.CurrentState != null, deferredArgs);
|
||||
|
||||
if (deferredArgs == null && _deferredEventArgs != null)
|
||||
{
|
||||
await _deferredEventArgs.DeferredTask.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accept)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Routing.RegisterImplicitPageRoutes(this);
|
||||
|
||||
|
||||
_accumulateNavigatedEvents = true;
|
||||
|
||||
var navigationRequest = ShellUriHandler.GetNavigationRequest(this, state.FullLocation, enableRelativeShellRoutes);
|
||||
var navigationRequest = ShellUriHandler.GetNavigationRequest(this, state.FullLocation, enableRelativeShellRoutes, shellNavigationParameters: shellNavigationParameters);
|
||||
var uri = navigationRequest.Request.FullUri;
|
||||
var queryString = navigationRequest.Query;
|
||||
var queryData = ParseQueryString(queryString);
|
||||
|
@ -508,14 +550,19 @@ namespace Xamarin.Forms
|
|||
var currentShellSection = CurrentItem?.CurrentItem;
|
||||
var nextActiveSection = shellSection ?? shellItem?.CurrentItem;
|
||||
|
||||
|
||||
ShellContent shellContent = navigationRequest.Request.Content;
|
||||
bool modalStackPreBuilt = false;
|
||||
|
||||
// If we're replacing the whole stack and there are global routes then build the navigation stack before setting the shell section visible
|
||||
if (navigationRequest.Request.GlobalRoutes.Count > 0 && nextActiveSection != null && navigationRequest.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt)
|
||||
if (navigationRequest.Request.GlobalRoutes.Count > 0 &&
|
||||
nextActiveSection != null &&
|
||||
navigationRequest.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt)
|
||||
{
|
||||
modalStackPreBuilt = true;
|
||||
await nextActiveSection.GoToAsync(navigationRequest, queryData, false);
|
||||
|
||||
bool? isAnimated = (nextActiveSection != currentShellSection) ? false : animate;
|
||||
await nextActiveSection.GoToAsync(navigationRequest, queryData, isAnimated);
|
||||
}
|
||||
|
||||
if (shellItem != null)
|
||||
|
@ -587,7 +634,7 @@ namespace Xamarin.Forms
|
|||
|
||||
// this can be null in the event that no navigation actually took place!
|
||||
if (_accumulatedEvent != null)
|
||||
ProcessNavigated(_accumulatedEvent);
|
||||
HandleNavigated(_accumulatedEvent);
|
||||
}
|
||||
|
||||
internal static void ApplyQueryAttributes(Element element, IDictionary<string, string> query, bool isLastItem)
|
||||
|
@ -633,7 +680,7 @@ namespace Xamarin.Forms
|
|||
element.SetValue(ShellContent.QueryAttributesProperty, query);
|
||||
}
|
||||
|
||||
ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> sectionStack, IReadOnlyList<Page> modalStack)
|
||||
internal static ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> sectionStack, IReadOnlyList<Page> modalStack)
|
||||
{
|
||||
List<string> routeStack = new List<string>();
|
||||
|
||||
|
@ -688,30 +735,33 @@ namespace Xamarin.Forms
|
|||
if (routeStack.Count > 0)
|
||||
routeStack.Insert(0, "/");
|
||||
|
||||
return String.Join("/", routeStack);
|
||||
return new ShellNavigationState(String.Join("/", routeStack), true);
|
||||
|
||||
|
||||
List<string> CollapsePath(
|
||||
string myRoute,
|
||||
List<string> currentRouteStack,
|
||||
IEnumerable<string> currentRouteStack,
|
||||
bool userDefinedRoute)
|
||||
{
|
||||
for (var i = currentRouteStack.Count - 1; i >= 0; i--)
|
||||
var localRouteStack = currentRouteStack.ToList();
|
||||
for (var i = localRouteStack.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var route = currentRouteStack[i];
|
||||
var route = localRouteStack[i];
|
||||
if (Routing.IsImplicit(route) ||
|
||||
(Routing.IsDefault(route) && userDefinedRoute))
|
||||
currentRouteStack.RemoveAt(i);
|
||||
{
|
||||
localRouteStack.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
var paths = myRoute.Split('/').ToList();
|
||||
|
||||
// collapse similar leaves
|
||||
int walkBackCurrentStackIndex = currentRouteStack.Count - (paths.Count - 1);
|
||||
int walkBackCurrentStackIndex = localRouteStack.Count - (paths.Count - 1);
|
||||
|
||||
while (paths.Count > 1 && walkBackCurrentStackIndex >= 0)
|
||||
{
|
||||
if (paths[0] == currentRouteStack[walkBackCurrentStackIndex])
|
||||
if (paths[0] == localRouteStack[walkBackCurrentStackIndex])
|
||||
{
|
||||
paths.RemoveAt(0);
|
||||
}
|
||||
|
@ -1100,7 +1150,7 @@ namespace Xamarin.Forms
|
|||
}
|
||||
|
||||
var args = new ShellNavigatingEventArgs(this.CurrentState, "", ShellNavigationSource.Pop, true);
|
||||
OnNavigating(args);
|
||||
HandleNavigating(args);
|
||||
return args.Cancelled;
|
||||
|
||||
async void NavigationPop()
|
||||
|
@ -1127,7 +1177,7 @@ namespace Xamarin.Forms
|
|||
}
|
||||
}
|
||||
|
||||
internal void ProcessNavigated(ShellNavigatedEventArgs args)
|
||||
internal void HandleNavigated(ShellNavigatedEventArgs args)
|
||||
{
|
||||
if (_accumulateNavigatedEvents)
|
||||
_accumulatedEvent = args;
|
||||
|
@ -1139,27 +1189,62 @@ namespace Xamarin.Forms
|
|||
{
|
||||
baseShellItem.OnAppearing(() =>
|
||||
{
|
||||
OnNavigated(args);
|
||||
Navigated?.Invoke(this, args);
|
||||
FireNavigatedEvents(args, this);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
OnNavigated(args);
|
||||
Navigated?.Invoke(this, args);
|
||||
FireNavigatedEvents(args, this);
|
||||
}
|
||||
|
||||
void FireNavigatedEvents(ShellNavigatedEventArgs a, Shell shell)
|
||||
{
|
||||
shell.OnNavigated(a);
|
||||
shell.Navigated?.Invoke(this, args);
|
||||
// reset active page route tree
|
||||
Routing.ClearImplicitPageRoutes();
|
||||
Routing.RegisterImplicitPageRoutes(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnNavigated(ShellNavigatedEventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
ShellNavigationState _lastNavigating;
|
||||
protected virtual void OnNavigating(ShellNavigatingEventArgs args)
|
||||
{
|
||||
Navigating?.Invoke(this, args);
|
||||
_lastNavigating = args.Target;
|
||||
|
||||
}
|
||||
|
||||
void HandleNavigating(ShellNavigatingEventArgs args)
|
||||
{
|
||||
if (!args.DeferredEventArgs)
|
||||
{
|
||||
_deferredEventArgs = null;
|
||||
Navigating?.Invoke(this, args);
|
||||
OnNavigating(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(args.DeferralCount > 0 && args.CanCancel)
|
||||
{
|
||||
_deferredEventArgs = args;
|
||||
args.RegisterDeferralCompletedCallBack(async () =>
|
||||
{
|
||||
_deferredEventArgs = null;
|
||||
if (args.Cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await CompleteDeferredNavigating(args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void OnCurrentItemChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
|
@ -1413,16 +1498,14 @@ namespace Xamarin.Forms
|
|||
}
|
||||
}
|
||||
|
||||
bool ProposeNavigation(ShellNavigationSource source, ShellNavigationState proposedState, bool canCancel)
|
||||
bool ProposeNavigation(ShellNavigationSource source, ShellNavigationState proposedState, bool canCancel, ShellNavigatingEventArgs deferredArgs)
|
||||
{
|
||||
if (_accumulateNavigatedEvents)
|
||||
return true;
|
||||
|
||||
var navArgs = new ShellNavigatingEventArgs(CurrentState, proposedState, source, canCancel);
|
||||
|
||||
OnNavigating(navArgs);
|
||||
//System.Diagnostics.Debug.WriteLine("Proposed: " + proposedState.Location);
|
||||
return !navArgs.Cancelled;
|
||||
var navArgs = deferredArgs ?? new ShellNavigatingEventArgs(CurrentState, proposedState, source, canCancel);
|
||||
HandleNavigating(navArgs);
|
||||
return !navArgs.Cancelled && navArgs.DeferralCount == 0;
|
||||
}
|
||||
|
||||
internal Element GetVisiblePage()
|
||||
|
@ -1523,7 +1606,6 @@ namespace Xamarin.Forms
|
|||
modal.NavigationProxy.Inner = new NavigationImplWrapper(modal.NavigationProxy.Inner, this);
|
||||
}
|
||||
|
||||
|
||||
class NavigationImplWrapper : NavigationProxy
|
||||
{
|
||||
readonly INavigation _shellProxy;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
public class ShellNavigatingDeferral
|
||||
{
|
||||
Action _completed;
|
||||
|
||||
internal ShellNavigatingDeferral(Action completed)
|
||||
{
|
||||
_completed = completed;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
var taskToComplete = Interlocked.Exchange(ref _completed, null);
|
||||
|
||||
if (taskToComplete != null)
|
||||
taskToComplete?.Invoke();
|
||||
}
|
||||
|
||||
internal bool IsCompleted => _completed == null;
|
||||
}
|
||||
}
|
|
@ -1,15 +1,22 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
public class ShellNavigatingEventArgs : EventArgs
|
||||
{
|
||||
int _deferalCount;
|
||||
Func<Task> _deferralFinishedTask;
|
||||
TaskCompletionSource<bool> _deferredTaskCompletionSource;
|
||||
|
||||
public ShellNavigatingEventArgs(ShellNavigationState current, ShellNavigationState target, ShellNavigationSource source, bool canCancel)
|
||||
{
|
||||
Current = current;
|
||||
Target = target;
|
||||
Source = source;
|
||||
CanCancel = canCancel;
|
||||
Animate = true;
|
||||
}
|
||||
|
||||
public ShellNavigationState Current { get; }
|
||||
|
@ -24,10 +31,62 @@ namespace Xamarin.Forms
|
|||
{
|
||||
if (!CanCancel)
|
||||
return false;
|
||||
|
||||
Cancelled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Cancelled { get; private set; }
|
||||
|
||||
public ShellNavigatingDeferral GetDeferral()
|
||||
{
|
||||
if (!CanCancel)
|
||||
return null;
|
||||
|
||||
DeferredEventArgs = true;
|
||||
var currentCount = Interlocked.Increment(ref _deferalCount);
|
||||
if(currentCount == 1)
|
||||
{
|
||||
_deferredTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
return new ShellNavigatingDeferral(DecrementDeferral);
|
||||
}
|
||||
|
||||
async void DecrementDeferral()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _deferalCount) == 0)
|
||||
{
|
||||
var task = _deferralFinishedTask();
|
||||
_deferralFinishedTask = null;
|
||||
|
||||
try
|
||||
{
|
||||
await task;
|
||||
_deferredTaskCompletionSource.SetResult(true);
|
||||
}
|
||||
catch(TaskCanceledException)
|
||||
{
|
||||
_deferredTaskCompletionSource.SetCanceled();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
_deferredTaskCompletionSource.SetException(exc);
|
||||
}
|
||||
|
||||
_deferredTaskCompletionSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal Task<bool> DeferredTask => _deferredTaskCompletionSource?.Task;
|
||||
internal bool Animate { get; set; }
|
||||
internal bool DeferredEventArgs { get; set; }
|
||||
|
||||
internal int DeferralCount => _deferalCount;
|
||||
|
||||
internal void RegisterDeferralCompletedCallBack(Func<Task> deferralFinishedTask)
|
||||
{
|
||||
_deferralFinishedTask = deferralFinishedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
{
|
||||
// At somepoint this will be shaped into a public API but for now we are using it
|
||||
// to simplify internal navigation parameters
|
||||
internal class ShellNavigationParameters
|
||||
{
|
||||
public ShellNavigatingEventArgs DeferredArgs { get; set; }
|
||||
public ShellNavigationState TargetState { get; set; }
|
||||
public bool EnableRelativeShellRoutes { get; set; }
|
||||
public bool? Animated { get; set; }
|
||||
|
||||
public bool PopAllPagesNotSpecifiedOnTargetState { get; set; }
|
||||
// This is used to service Navigation.PushAsync style APIs where the user doesn't use routes at all
|
||||
public Page PagePushing { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
|
@ -15,7 +16,6 @@ namespace Xamarin.Forms
|
|||
set
|
||||
{
|
||||
_fullLocation = value;
|
||||
Location = Routing.Remove(value, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,11 @@ namespace Xamarin.Forms
|
|||
}
|
||||
|
||||
public ShellNavigationState() { }
|
||||
public ShellNavigationState(string location)
|
||||
public ShellNavigationState(string location) : this(location, false)
|
||||
{
|
||||
}
|
||||
|
||||
internal ShellNavigationState(string location, bool trimForUser)
|
||||
{
|
||||
var uri = ShellUriHandler.CreateUri(location);
|
||||
|
||||
|
@ -34,10 +38,54 @@ namespace Xamarin.Forms
|
|||
uri = new Uri($"/{uri.PathAndQuery}", UriKind.Relative);
|
||||
|
||||
FullLocation = uri;
|
||||
|
||||
if (trimForUser)
|
||||
Location = TrimDownImplicitAndDefaultPaths(FullLocation);
|
||||
else
|
||||
Location = FullLocation;
|
||||
}
|
||||
|
||||
public ShellNavigationState(Uri location) => FullLocation = location;
|
||||
public static implicit operator ShellNavigationState(Uri uri) => new ShellNavigationState(uri);
|
||||
public static implicit operator ShellNavigationState(string value) => new ShellNavigationState(value);
|
||||
|
||||
static Uri TrimDownImplicitAndDefaultPaths(Uri uri)
|
||||
{
|
||||
uri = ShellUriHandler.FormatUri(uri, null);
|
||||
|
||||
// don't trim relative pushes
|
||||
if (!uri.OriginalString.StartsWith($"{Routing.PathSeparator}{Routing.PathSeparator}"))
|
||||
return uri;
|
||||
|
||||
string[] parts = uri.OriginalString.TrimEnd(Routing.PathSeparator[0]).Split(Routing.PathSeparator[0]);
|
||||
|
||||
List<string> toKeep = new List<string>();
|
||||
|
||||
// iterate over the shellitem/section/content
|
||||
for (int i = 2; i < 5 && i < parts.Length; i++)
|
||||
{
|
||||
if (!(Routing.IsDefault(parts[i])) && !(Routing.IsImplicit(parts[i])))
|
||||
{
|
||||
toKeep.Add(parts[i]);
|
||||
}
|
||||
else if (i == 4)
|
||||
{
|
||||
// if all the routes are default then just put the last
|
||||
// shell content page as the route
|
||||
if (toKeep.Count == 0)
|
||||
toKeep.Add(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Always include pushed pages
|
||||
for (int i = 5; i < parts.Length; i++)
|
||||
{
|
||||
toKeep.Add(parts[i]);
|
||||
}
|
||||
|
||||
toKeep.Insert(0, "");
|
||||
toKeep.Insert(0, "");
|
||||
return new Uri(string.Join(Routing.PathSeparator, toKeep), UriKind.Relative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,7 +340,11 @@ namespace Xamarin.Forms
|
|||
|
||||
if (globalRoutes == null || globalRoutes.Count == 0)
|
||||
{
|
||||
await Navigation.PopToRootAsync(animate ?? false);
|
||||
if(_navStack.Count == 2)
|
||||
await OnPopAsync(animate ?? false);
|
||||
else
|
||||
await OnPopToRootAsync(animate ?? false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -385,17 +389,18 @@ namespace Xamarin.Forms
|
|||
IsPoppingModalStack = true;
|
||||
while (navStack.Count > popCount)
|
||||
{
|
||||
bool isAnimated = animate ?? IsNavigationAnimated(navStack[navStack.Count - 1]);
|
||||
if (Navigation.ModalStack.Contains(navStack[navStack.Count - 1]))
|
||||
{
|
||||
await Navigation.PopModalAsync(false);
|
||||
await Navigation.PopModalAsync(isAnimated);
|
||||
}
|
||||
else if (Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
await Navigation.ModalStack[Navigation.ModalStack.Count - 1].Navigation.PopAsync(false);
|
||||
await Navigation.ModalStack[Navigation.ModalStack.Count - 1].Navigation.PopAsync(isAnimated);
|
||||
}
|
||||
else
|
||||
{
|
||||
await OnPopAsync(false);
|
||||
await OnPopAsync(isAnimated);
|
||||
}
|
||||
|
||||
navStack = BuildFlattenedNavigationStack(new List<Page>(_navStack), Navigation?.ModalStack);
|
||||
|
@ -426,7 +431,9 @@ namespace Xamarin.Forms
|
|||
route = globalRoutes[i];
|
||||
var content = Routing.GetOrCreateContent(route) as Page;
|
||||
if (content == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var isModal = (Shell.GetPresentationMode(content) & PresentationMode.Modal) == PresentationMode.Modal;
|
||||
|
||||
|
@ -438,7 +445,7 @@ namespace Xamarin.Forms
|
|||
else if (modalPageStacks.Count > 0)
|
||||
{
|
||||
if (modalPageStacks[modalPageStacks.Count - 1] is NavigationPage navigationPage)
|
||||
await navigationPage.Navigation.PushAsync(content);
|
||||
await navigationPage.Navigation.PushAsync(content, animate ?? IsNavigationAnimated(content));
|
||||
else
|
||||
throw new InvalidOperationException($"Shell cannot push a page to the following type: {modalPageStacks[modalPageStacks.Count - 1]}. The visible modal page needs to be a NavigationPage");
|
||||
}
|
||||
|
@ -451,7 +458,7 @@ namespace Xamarin.Forms
|
|||
for (int i = Navigation.ModalStack.Count; i < modalPageStacks.Count; i++)
|
||||
{
|
||||
bool isLast = i == modalPageStacks.Count - 1;
|
||||
bool isAnimated = animate ?? (Shell.GetPresentationMode(modalPageStacks[i]) & PresentationMode.NotAnimated) != PresentationMode.NotAnimated;
|
||||
bool isAnimated = animate ?? IsNavigationAnimated(modalPageStacks[i]);
|
||||
IsPushingModalStack = !isLast;
|
||||
await ((NavigationImpl)Navigation).PushModalAsync(modalPageStacks[i], isAnimated);
|
||||
}
|
||||
|
@ -462,7 +469,7 @@ namespace Xamarin.Forms
|
|||
|
||||
if(isLast)
|
||||
{
|
||||
bool isAnimated = animate ?? (Shell.GetPresentationMode(nonModalPageStacks[i]) & PresentationMode.NotAnimated) != PresentationMode.NotAnimated;
|
||||
bool isAnimated = animate ?? IsNavigationAnimated(nonModalPageStacks[i]);
|
||||
await OnPushAsync(nonModalPageStacks[i], isAnimated);
|
||||
}
|
||||
else
|
||||
|
@ -474,6 +481,11 @@ namespace Xamarin.Forms
|
|||
shell.UpdateCurrentState(ShellNavigationSource.ShellSectionChanged);
|
||||
}
|
||||
|
||||
bool IsNavigationAnimated(BindableObject bo)
|
||||
{
|
||||
return (Shell.GetPresentationMode(bo) & PresentationMode.NotAnimated) != PresentationMode.NotAnimated;
|
||||
}
|
||||
|
||||
List<Page> BuildFlattenedNavigationStack(List<Page> startingList, IReadOnlyList<Page> modalStack)
|
||||
{
|
||||
if (modalStack == null)
|
||||
|
@ -940,11 +952,68 @@ namespace Xamarin.Forms
|
|||
|
||||
protected override void OnInsertPageBefore(Page page, Page before) => _owner.OnInsertPageBefore(page, before);
|
||||
|
||||
protected override Task<Page> OnPopAsync(bool animated) => _owner.OnPopAsync(animated);
|
||||
protected override async Task<Page> OnPopAsync(bool animated)
|
||||
{
|
||||
if (!_owner.IsVisibleSection)
|
||||
{
|
||||
return (await _owner.OnPopAsync(animated));
|
||||
}
|
||||
|
||||
protected override Task OnPopToRootAsync(bool animated) => _owner.OnPopToRootAsync(animated);
|
||||
var navigationParameters = new ShellNavigationParameters()
|
||||
{
|
||||
Animated = animated,
|
||||
TargetState = ".."
|
||||
};
|
||||
|
||||
protected override Task OnPushAsync(Page page, bool animated) => _owner.OnPushAsync(page, animated);
|
||||
var returnedPage = (_owner as IShellSectionController).PresentedPage;
|
||||
await _owner.Shell.GoToAsync(navigationParameters);
|
||||
|
||||
// This means the page wasn't popped and navigation was cancelled
|
||||
if ((_owner as IShellSectionController).PresentedPage == returnedPage)
|
||||
return null;
|
||||
|
||||
return returnedPage;
|
||||
}
|
||||
|
||||
protected override Task OnPopToRootAsync(bool animated)
|
||||
{
|
||||
if (!_owner.IsVisibleSection)
|
||||
{
|
||||
return _owner.OnPopToRootAsync(animated);
|
||||
}
|
||||
|
||||
var shell = _owner.Shell;
|
||||
var targetState =
|
||||
Shell.GetNavigationState(
|
||||
shell.CurrentItem,
|
||||
_owner,
|
||||
_owner.CurrentItem,
|
||||
null,
|
||||
null);
|
||||
|
||||
var navigationParameters = new ShellNavigationParameters()
|
||||
{
|
||||
Animated = animated,
|
||||
TargetState = targetState,
|
||||
PopAllPagesNotSpecifiedOnTargetState = true
|
||||
};
|
||||
|
||||
return _owner.Shell.GoToAsync(navigationParameters);
|
||||
}
|
||||
|
||||
protected override Task OnPushAsync(Page page, bool animated)
|
||||
{
|
||||
if (!_owner.IsVisibleSection)
|
||||
return _owner.OnPushAsync(page, animated);
|
||||
|
||||
var navigationParameters = new ShellNavigationParameters()
|
||||
{
|
||||
Animated = animated,
|
||||
PagePushing = page
|
||||
};
|
||||
|
||||
return (_owner.Shell).GoToAsync(navigationParameters);
|
||||
}
|
||||
|
||||
protected override void OnRemovePage(Page page) => _owner.OnRemovePage(page);
|
||||
}
|
||||
|
|
|
@ -79,9 +79,10 @@ namespace Xamarin.Forms
|
|||
return new Uri(uri);
|
||||
}
|
||||
|
||||
internal static NavigationRequest GetNavigationRequest(Shell shell, Uri uri, bool enableRelativeShellRoutes = false, bool throwNavigationErrorAsException = true)
|
||||
internal static NavigationRequest GetNavigationRequest(Shell shell, Uri uri, bool enableRelativeShellRoutes = false, bool throwNavigationErrorAsException = true, ShellNavigationParameters shellNavigationParameters = null)
|
||||
{
|
||||
uri = FormatUri(uri, shell);
|
||||
|
||||
// figure out the intent of the Uri
|
||||
NavigationRequest.WhatToDoWithTheStack whatDoIDo = NavigationRequest.WhatToDoWithTheStack.PushToIt;
|
||||
if (uri.IsAbsoluteUri)
|
||||
|
@ -93,6 +94,7 @@ namespace Xamarin.Forms
|
|||
|
||||
Uri request = ConvertToStandardFormat(shell, uri);
|
||||
|
||||
|
||||
var possibleRouteMatches = GenerateRoutePaths(shell, request, uri, enableRelativeShellRoutes);
|
||||
|
||||
|
||||
|
@ -822,6 +824,4 @@ namespace Xamarin.Forms
|
|||
public ShellContent Content { get; }
|
||||
public List<string> GlobalRoutes { get; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -97,5 +97,10 @@ namespace Xamarin.Forms
|
|||
}
|
||||
|
||||
protected object GetTemplateChild(string name) => TemplateUtilities.GetTemplateChild(this, name);
|
||||
|
||||
public virtual ControlTemplate ResolveControlTemplate()
|
||||
{
|
||||
return ControlTemplate;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,7 +58,49 @@ namespace Xamarin.Forms
|
|||
|
||||
internal static readonly BindableProperty TransformProperty = BindableProperty.Create("Transform", typeof(string), typeof(VisualElement), null, propertyChanged: OnTransformChanged);
|
||||
|
||||
public static readonly BindableProperty ClipProperty = BindableProperty.Create(nameof(Clip), typeof(Geometry), typeof(VisualElement), null);
|
||||
public static readonly BindableProperty ClipProperty = BindableProperty.Create(nameof(Clip), typeof(Geometry), typeof(VisualElement), null,
|
||||
propertyChanging: (bindable, oldvalue, newvalue) =>
|
||||
{
|
||||
if (oldvalue != null)
|
||||
(bindable as VisualElement)?.StopNotifyingClipChanges();
|
||||
},
|
||||
propertyChanged: (bindable, oldvalue, newvalue) =>
|
||||
{
|
||||
if (newvalue != null)
|
||||
(bindable as VisualElement)?.NotifyClipChanges();
|
||||
});
|
||||
|
||||
void NotifyClipChanges()
|
||||
{
|
||||
if (Clip != null)
|
||||
{
|
||||
Clip.PropertyChanged += OnClipChanged;
|
||||
|
||||
if (Clip is GeometryGroup geometryGroup)
|
||||
geometryGroup.InvalidateGeometryRequested += InvalidateGeometryRequested;
|
||||
}
|
||||
}
|
||||
|
||||
void StopNotifyingClipChanges()
|
||||
{
|
||||
if (Clip != null)
|
||||
{
|
||||
Clip.PropertyChanged -= OnClipChanged;
|
||||
|
||||
if(Clip is GeometryGroup geometryGroup)
|
||||
geometryGroup.InvalidateGeometryRequested -= InvalidateGeometryRequested;
|
||||
}
|
||||
}
|
||||
|
||||
void OnClipChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(Clip));
|
||||
}
|
||||
|
||||
void InvalidateGeometryRequested(object sender, EventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(Clip));
|
||||
}
|
||||
|
||||
public static readonly BindableProperty VisualProperty =
|
||||
BindableProperty.Create(nameof(Visual), typeof(IVisual), typeof(VisualElement), Forms.VisualMarker.MatchParent,
|
||||
|
|
|
@ -139,6 +139,8 @@ namespace Xamarin.Forms.Platform.Android
|
|||
RegisterHandler(typeof(Image), typeof(FastRenderers.ImageRenderer), typeof(ImageRenderer));
|
||||
RegisterHandler(typeof(Frame), typeof(FastRenderers.FrameRenderer), typeof(FrameRenderer));
|
||||
}
|
||||
|
||||
Registrar.Registered.Register(typeof(RadioButton), typeof(RadioButtonRenderer));
|
||||
}
|
||||
|
||||
protected void LoadApplication(Application application)
|
||||
|
|
|
@ -8,7 +8,6 @@ using Android.Util;
|
|||
using Android.Views;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.Platform.Android.FastRenderers;
|
||||
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
|
||||
using AColor = Android.Graphics.Color;
|
||||
using AView = Android.Views.View;
|
||||
using Android.Widget;
|
||||
|
@ -16,8 +15,8 @@ using Android.Widget;
|
|||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
public class RadioButtonRenderer : AppCompatRadioButton,
|
||||
IBorderVisualElementRenderer, IButtonLayoutRenderer, IVisualElementRenderer, IViewRenderer, ITabStop,
|
||||
AView.IOnAttachStateChangeListener, AView.IOnFocusChangeListener, AView.IOnClickListener, AView.IOnTouchListener,
|
||||
IBorderVisualElementRenderer, IVisualElementRenderer, IViewRenderer, ITabStop,
|
||||
AView.IOnFocusChangeListener,
|
||||
CompoundButton.IOnCheckedChangeListener
|
||||
{
|
||||
float _defaultFontSize;
|
||||
|
@ -30,9 +29,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
VisualElementTracker _tracker;
|
||||
VisualElementRenderer _visualElementRenderer;
|
||||
BorderBackgroundManager _backgroundTracker;
|
||||
ButtonLayoutManager _buttonLayoutManager;
|
||||
IPlatformElementConfiguration<PlatformConfiguration.Android, Button> _platformElementConfiguration;
|
||||
Button _button;
|
||||
IPlatformElementConfiguration<PlatformConfiguration.Android, RadioButton> _platformElementConfiguration;
|
||||
|
||||
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
|
||||
public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
|
||||
|
@ -42,7 +39,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
Initialize();
|
||||
}
|
||||
|
||||
protected Button Element => Button;
|
||||
protected RadioButton Element { get; set; }
|
||||
protected AppCompatRadioButton Control => this;
|
||||
|
||||
VisualElement IBorderVisualElementRenderer.Element => Element;
|
||||
|
@ -52,36 +49,17 @@ namespace Xamarin.Forms.Platform.Android
|
|||
ViewGroup IVisualElementRenderer.ViewGroup => null;
|
||||
VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
|
||||
|
||||
Button Button
|
||||
{
|
||||
get => _button;
|
||||
set
|
||||
{
|
||||
_button = value;
|
||||
_platformElementConfiguration = null;
|
||||
}
|
||||
}
|
||||
|
||||
AView ITabStop.TabStop => this;
|
||||
|
||||
void IOnClickListener.OnClick(AView v) => ButtonElementManager.OnClick(Button, Button, v);
|
||||
|
||||
bool IOnTouchListener.OnTouch(AView v, MotionEvent e) => ButtonElementManager.OnTouch(Button, Button, v, e);
|
||||
|
||||
void IOnAttachStateChangeListener.OnViewAttachedToWindow(AView attachedView) =>
|
||||
_buttonLayoutManager.OnViewAttachedToWindow(attachedView);
|
||||
|
||||
void IOnAttachStateChangeListener.OnViewDetachedFromWindow(AView detachedView) =>
|
||||
_buttonLayoutManager.OnViewDetachedFromWindow(detachedView);
|
||||
|
||||
void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus)
|
||||
{
|
||||
((IElementController)Button).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
|
||||
((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
|
||||
}
|
||||
|
||||
SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
|
||||
{
|
||||
return _buttonLayoutManager.GetDesiredSize(widthConstraint, heightConstraint);
|
||||
Measure(widthConstraint, heightConstraint);
|
||||
return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight));
|
||||
}
|
||||
|
||||
void IVisualElementRenderer.SetElement(VisualElement element)
|
||||
|
@ -91,13 +69,13 @@ namespace Xamarin.Forms.Platform.Android
|
|||
throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
if (!(element is Button))
|
||||
if (!(element is RadioButton))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(element)} must be of type {nameof(Button)}");
|
||||
throw new ArgumentException($"{nameof(element)} must be of type {nameof(RadioButton)}");
|
||||
}
|
||||
|
||||
VisualElement oldElement = Button;
|
||||
Button = (Button)element;
|
||||
RadioButton oldElement = Element;
|
||||
Element = (RadioButton)element;
|
||||
|
||||
Performance.Start(out string reference);
|
||||
|
||||
|
@ -106,7 +84,6 @@ namespace Xamarin.Forms.Platform.Android
|
|||
oldElement.PropertyChanged -= OnElementPropertyChanged;
|
||||
}
|
||||
|
||||
|
||||
element.PropertyChanged += OnElementPropertyChanged;
|
||||
|
||||
if (_tracker == null)
|
||||
|
@ -119,7 +96,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
_visualElementRenderer = new VisualElementRenderer(this);
|
||||
}
|
||||
|
||||
OnElementChanged(new ElementChangedEventArgs<Button>(oldElement as Button, Button));
|
||||
OnElementChanged(new ElementChangedEventArgs<RadioButton>(oldElement, Element));
|
||||
|
||||
SendVisualElementInitialized(element, this);
|
||||
|
||||
|
@ -156,7 +133,6 @@ namespace Xamarin.Forms.Platform.Android
|
|||
{
|
||||
SetOnClickListener(null);
|
||||
SetOnTouchListener(null);
|
||||
RemoveOnAttachStateChangeListener(this);
|
||||
OnFocusChangeListener = null;
|
||||
SetOnCheckedChangeListener(null);
|
||||
|
||||
|
@ -170,8 +146,6 @@ namespace Xamarin.Forms.Platform.Android
|
|||
_visualElementRenderer?.Dispose();
|
||||
_backgroundTracker?.Dispose();
|
||||
_backgroundTracker = null;
|
||||
_buttonLayoutManager?.Dispose();
|
||||
_buttonLayoutManager = null;
|
||||
|
||||
if (Element != null)
|
||||
{
|
||||
|
@ -191,7 +165,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return base.OnTouchEvent(e);
|
||||
}
|
||||
|
||||
protected virtual void OnElementChanged(ElementChangedEventArgs<Button> e)
|
||||
protected virtual void OnElementChanged(ElementChangedEventArgs<RadioButton> e)
|
||||
{
|
||||
if (e.NewElement != null && !_isDisposed)
|
||||
{
|
||||
|
@ -204,9 +178,8 @@ namespace Xamarin.Forms.Platform.Android
|
|||
UpdateTextColor();
|
||||
UpdateInputTransparent();
|
||||
UpdateBackgroundColor();
|
||||
_buttonLayoutManager?.Update();
|
||||
//UpdateButtonImage(true);
|
||||
UpdateIsChecked();
|
||||
UpdateContent();
|
||||
ElevationHelper.SetElevation(this, e.NewElement);
|
||||
}
|
||||
|
||||
|
@ -215,11 +188,11 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == Button.TextColorProperty.PropertyName)
|
||||
if (e.PropertyName == RadioButton.TextColorProperty.PropertyName)
|
||||
{
|
||||
UpdateTextColor();
|
||||
}
|
||||
else if (e.PropertyName == Button.FontProperty.PropertyName)
|
||||
else if (e.IsOneOf(RadioButton.FontAttributesProperty, RadioButton.FontFamilyProperty, RadioButton.FontSizeProperty))
|
||||
{
|
||||
UpdateFont();
|
||||
}
|
||||
|
@ -231,20 +204,14 @@ namespace Xamarin.Forms.Platform.Android
|
|||
{
|
||||
UpdateIsChecked();
|
||||
}
|
||||
//else if (e.PropertyName == RadioButton.ButtonSourceProperty.PropertyName)
|
||||
//{
|
||||
// UpdateButtonImage(false);
|
||||
//}
|
||||
else if (e.PropertyName == RadioButton.ContentProperty.PropertyName)
|
||||
{
|
||||
UpdateContent();
|
||||
}
|
||||
|
||||
ElementPropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected override void OnLayout(bool changed, int l, int t, int r, int b)
|
||||
{
|
||||
_buttonLayoutManager?.OnLayout(changed, l, t, r, b);
|
||||
base.OnLayout(changed, l, t, r, b);
|
||||
}
|
||||
|
||||
void SetTracker(VisualElementTracker tracker)
|
||||
{
|
||||
_tracker = tracker;
|
||||
|
@ -267,13 +234,9 @@ namespace Xamarin.Forms.Platform.Android
|
|||
void Initialize()
|
||||
{
|
||||
_automationPropertiesProvider = new AutomationPropertiesProvider(this);
|
||||
_buttonLayoutManager = new ButtonLayoutManager(this);
|
||||
_backgroundTracker = new BorderBackgroundManager(this);
|
||||
|
||||
SoundEffectsEnabled = false;
|
||||
SetOnClickListener(this);
|
||||
SetOnTouchListener(this);
|
||||
AddOnAttachStateChangeListener(this);
|
||||
OnFocusChangeListener = this;
|
||||
SetOnCheckedChangeListener(this);
|
||||
|
||||
|
@ -287,7 +250,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return;
|
||||
}
|
||||
|
||||
Font font = Button.Font;
|
||||
Font font = Font.OfSize(Element.FontFamily, Element.FontSize).WithAttributes(Element.FontAttributes);
|
||||
|
||||
if (font == Font.Default && _defaultFontSize == 0f)
|
||||
{
|
||||
|
@ -329,33 +292,9 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return;
|
||||
}
|
||||
|
||||
_textColorSwitcher.Value.UpdateTextColor(this, Button.TextColor);
|
||||
_textColorSwitcher.Value.UpdateTextColor(this, Element.TextColor);
|
||||
}
|
||||
|
||||
// TODO Needs implementations beyond Android
|
||||
//void UpdateButtonImage(bool isInitializing)
|
||||
//{
|
||||
// if (Element == null || _isDisposed)
|
||||
// return;
|
||||
|
||||
// ImageSource buttonSource = ((RadioButton)Element).ButtonSource;
|
||||
// if (buttonSource != null && !buttonSource.IsEmpty)
|
||||
// {
|
||||
// Drawable currButtonImage = Control.ButtonDrawable;
|
||||
|
||||
// this.ApplyDrawableAsync(RadioButton.ButtonSourceProperty, Context, image =>
|
||||
// {
|
||||
// if (image == currButtonImage)
|
||||
// return;
|
||||
// Control.SetButtonDrawable(image);
|
||||
|
||||
// Element.InvalidateMeasureNonVirtual(InvalidationTrigger.MeasureChanged);
|
||||
// });
|
||||
// }
|
||||
// else if(!isInitializing)
|
||||
// Control.SetButtonDrawable(null);
|
||||
//}
|
||||
|
||||
void UpdateIsChecked()
|
||||
{
|
||||
if (Element == null || Control == null)
|
||||
|
@ -364,6 +303,16 @@ namespace Xamarin.Forms.Platform.Android
|
|||
Checked = ((RadioButton)Element).IsChecked;
|
||||
}
|
||||
|
||||
void UpdateContent()
|
||||
{
|
||||
if (Element == null || Control == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Control.Text = Element.ContentAsString();
|
||||
}
|
||||
|
||||
void IOnCheckedChangeListener.OnCheckedChanged(CompoundButton buttonView, bool isChecked)
|
||||
{
|
||||
((IElementController)Element).SetValueFromRenderer(RadioButton.IsCheckedProperty, isChecked);
|
||||
|
@ -373,22 +322,18 @@ namespace Xamarin.Forms.Platform.Android
|
|||
float IBorderVisualElementRenderer.ShadowDx => ShadowDx;
|
||||
float IBorderVisualElementRenderer.ShadowDy => ShadowDy;
|
||||
AColor IBorderVisualElementRenderer.ShadowColor => ShadowColor;
|
||||
bool IBorderVisualElementRenderer.UseDefaultPadding() => OnThisPlatform().UseDefaultPadding();
|
||||
bool IBorderVisualElementRenderer.UseDefaultShadow() => OnThisPlatform().UseDefaultShadow();
|
||||
bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
|
||||
AView IBorderVisualElementRenderer.View => this;
|
||||
|
||||
IPlatformElementConfiguration<PlatformConfiguration.Android, Button> OnThisPlatform()
|
||||
IPlatformElementConfiguration<PlatformConfiguration.Android, RadioButton> OnThisPlatform()
|
||||
{
|
||||
if (_platformElementConfiguration == null)
|
||||
_platformElementConfiguration = Button.OnThisPlatform();
|
||||
_platformElementConfiguration = Element.OnThisPlatform();
|
||||
|
||||
return _platformElementConfiguration;
|
||||
}
|
||||
|
||||
AppCompatButton IButtonLayoutRenderer.View => null;
|
||||
|
||||
Button IButtonLayoutRenderer.Element => this.Element;
|
||||
|
||||
bool IBorderVisualElementRenderer.UseDefaultPadding() => true;
|
||||
bool IBorderVisualElementRenderer.UseDefaultShadow() => true;
|
||||
}
|
||||
}
|
|
@ -338,8 +338,18 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
internal static IVisualElementRenderer CreateRenderer(VisualElement element, Context context)
|
||||
{
|
||||
IVisualElementRenderer renderer = Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element, context)
|
||||
?? new DefaultRenderer(context);
|
||||
IVisualElementRenderer renderer = null;
|
||||
|
||||
if (element is TemplatedView tv && tv.ResolveControlTemplate() != null)
|
||||
{
|
||||
renderer = new DefaultRenderer(context);
|
||||
}
|
||||
|
||||
if (renderer == null)
|
||||
{
|
||||
renderer = Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element, context)
|
||||
?? new DefaultRenderer(context);
|
||||
}
|
||||
|
||||
renderer.SetElement(element);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ using System.Runtime.CompilerServices;
|
|||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Internals;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using static Xamarin.Forms.Platform.Android.Platform;
|
||||
|
||||
// These renderers are now registered via the RenderWithAttribute in the Android Forwarders project.
|
||||
// Note that AppCompat and FastRenderers are also registered conditionally in FormsAppCompatActivity.LoadApplication
|
||||
|
|
|
@ -30,13 +30,6 @@ namespace Xamarin.Forms.Platform.MacOS
|
|||
if (Control != null)
|
||||
Control.Activated -= OnButtonActivated;
|
||||
|
||||
var formsButton = Control as FormsNSButton;
|
||||
if (formsButton != null)
|
||||
{
|
||||
formsButton.Pressed -= HandleButtonPressed;
|
||||
formsButton.Released -= HandleButtonReleased;
|
||||
}
|
||||
|
||||
ObserveStateChange(false);
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
@ -52,15 +45,13 @@ namespace Xamarin.Forms.Platform.MacOS
|
|||
{
|
||||
var btn = new FormsNSButton();
|
||||
btn.SetButtonType(NSButtonType.Radio);
|
||||
btn.Pressed += HandleButtonPressed;
|
||||
btn.Released += HandleButtonReleased;
|
||||
SetNativeControl(btn);
|
||||
ObserveStateChange(true);
|
||||
|
||||
Control.Activated += OnButtonActivated;
|
||||
}
|
||||
|
||||
UpdateText();
|
||||
UpdateContent();
|
||||
UpdateFont();
|
||||
UpdateBorder();
|
||||
}
|
||||
|
@ -70,9 +61,11 @@ namespace Xamarin.Forms.Platform.MacOS
|
|||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
|
||||
if (e.PropertyName == RadioButton.TextProperty.PropertyName || e.PropertyName == RadioButton.TextColorProperty.PropertyName)
|
||||
UpdateText();
|
||||
else if (e.PropertyName == RadioButton.FontProperty.PropertyName)
|
||||
if (e.PropertyName == RadioButton.ContentProperty.PropertyName || e.PropertyName == RadioButton.TextColorProperty.PropertyName)
|
||||
UpdateContent();
|
||||
else if (e.PropertyName == RadioButton.FontAttributesProperty.PropertyName
|
||||
|| e.PropertyName == RadioButton.FontFamilyProperty.PropertyName
|
||||
|| e.PropertyName == RadioButton.FontSizeProperty.PropertyName)
|
||||
UpdateFont();
|
||||
else if (e.PropertyName == RadioButton.BorderWidthProperty.PropertyName ||
|
||||
e.PropertyName == RadioButton.CornerRadiusProperty.PropertyName ||
|
||||
|
@ -113,19 +106,23 @@ namespace Xamarin.Forms.Platform.MacOS
|
|||
|
||||
void UpdateFont()
|
||||
{
|
||||
Control.Font = Element.Font.ToNSFont();
|
||||
Font font = Font.OfSize(Element.FontFamily, Element.FontSize).WithAttributes(Element.FontAttributes);
|
||||
Control.Font = font.ToNSFont();
|
||||
}
|
||||
|
||||
void UpdateText()
|
||||
void UpdateContent()
|
||||
{
|
||||
var text = Element.ContentAsString();
|
||||
|
||||
var color = Element.TextColor;
|
||||
if (color == Color.Default)
|
||||
{
|
||||
Control.Title = Element.Text ?? "";
|
||||
Control.Title = text ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
var textWithColor = new NSAttributedString(Element.Text ?? "", font: Element.Font.ToNSFont(), foregroundColor: color.ToNSColor(), paragraphStyle: new NSMutableParagraphStyle() { Alignment = NSTextAlignment.Center });
|
||||
Font font = Font.OfSize(Element.FontFamily, Element.FontSize).WithAttributes(Element.FontAttributes);
|
||||
var textWithColor = new NSAttributedString(text ?? "", font: font.ToNSFont(), foregroundColor: color.ToNSColor(), paragraphStyle: new NSMutableParagraphStyle() { Alignment = NSTextAlignment.Center });
|
||||
Control.AttributedTitle = textWithColor;
|
||||
}
|
||||
}
|
||||
|
@ -135,19 +132,6 @@ namespace Xamarin.Forms.Platform.MacOS
|
|||
Control.State = Element.IsChecked ? NSCellStateValue.On : NSCellStateValue.Off;
|
||||
}
|
||||
|
||||
void HandleButtonPressed()
|
||||
{
|
||||
Element?.SendPressed();
|
||||
|
||||
if (!Element.IsChecked)
|
||||
Element.IsChecked = !Element.IsChecked;
|
||||
}
|
||||
|
||||
void HandleButtonReleased()
|
||||
{
|
||||
Element?.SendReleased();
|
||||
}
|
||||
|
||||
void ObserveStateChange(bool observe)
|
||||
{
|
||||
if (observe)
|
||||
|
|
|
@ -11,9 +11,11 @@ namespace Xamarin.Forms.Platform.Tizen
|
|||
public RadioButtonRenderer()
|
||||
{
|
||||
RegisterPropertyHandler(RadioButton.IsCheckedProperty, UpdateIsChecked);
|
||||
RegisterPropertyHandler(RadioButton.TextProperty, UpdateText);
|
||||
RegisterPropertyHandler(RadioButton.ContentProperty, UpdateText);
|
||||
RegisterPropertyHandler(RadioButton.TextColorProperty, UpdateTextColor);
|
||||
RegisterPropertyHandler(RadioButton.FontProperty, UpdateFont);
|
||||
RegisterPropertyHandler(RadioButton.FontFamilyProperty, UpdateFont);
|
||||
RegisterPropertyHandler(RadioButton.FontAttributesProperty, UpdateFont);
|
||||
RegisterPropertyHandler(RadioButton.FontSizeProperty, UpdateFont);
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<RadioButton> e)
|
||||
|
@ -69,7 +71,7 @@ namespace Xamarin.Forms.Platform.Tizen
|
|||
|
||||
void UpdateText(bool isInitialized)
|
||||
{
|
||||
_span.Text = Element.Text;
|
||||
_span.Text = Element.ContentAsString();
|
||||
if (!isInitialized)
|
||||
ApplyTextAndStyle();
|
||||
}
|
||||
|
|
|
@ -42,8 +42,19 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
IVisualElementRenderer renderer = Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element) ??
|
||||
new DefaultRenderer();
|
||||
IVisualElementRenderer renderer = null;
|
||||
|
||||
if (element is TemplatedView tv && tv.ResolveControlTemplate() != null)
|
||||
{
|
||||
renderer = new DefaultRenderer();
|
||||
}
|
||||
|
||||
if (renderer == null)
|
||||
{
|
||||
renderer = Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element) ??
|
||||
new DefaultRenderer();
|
||||
}
|
||||
|
||||
renderer.SetElement(element);
|
||||
return renderer;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Windows.Devices.Radios;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using static Xamarin.Forms.Platform.UWP.ViewToRendererConverter;
|
||||
using WBrush = Windows.UI.Xaml.Media.Brush;
|
||||
using WThickness = Windows.UI.Xaml.Thickness;
|
||||
|
||||
|
@ -20,8 +23,6 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
{
|
||||
var button = new FormsRadioButton();
|
||||
|
||||
button.Click += OnButtonClick;
|
||||
button.AddHandler(PointerPressedEvent, new PointerEventHandler(OnPointerPressed), true);
|
||||
button.Loaded += ButtonOnLoaded;
|
||||
button.Checked += OnRadioButtonCheckedOrUnchecked;
|
||||
button.Unchecked += OnRadioButtonCheckedOrUnchecked;
|
||||
|
@ -79,7 +80,7 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
|
||||
if (e.PropertyName == RadioButton.TextProperty.PropertyName || e.PropertyName == Button.ImageSourceProperty.PropertyName)
|
||||
if (e.PropertyName == RadioButton.ContentProperty.PropertyName || e.PropertyName == Button.ImageSourceProperty.PropertyName)
|
||||
{
|
||||
UpdateContent();
|
||||
}
|
||||
|
@ -91,7 +92,7 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
{
|
||||
UpdateTextColor();
|
||||
}
|
||||
else if (e.PropertyName == RadioButton.FontProperty.PropertyName)
|
||||
else if (e.IsOneOf(RadioButton.FontFamilyProperty, RadioButton.FontSizeProperty, RadioButton.FontAttributesProperty))
|
||||
{
|
||||
UpdateFont();
|
||||
}
|
||||
|
@ -132,17 +133,6 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
|
||||
protected override bool PreventGestureBubbling { get; set; } = true;
|
||||
|
||||
void OnButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((IButtonController)Element)?.SendReleased();
|
||||
((IButtonController)Element)?.SendClicked();
|
||||
}
|
||||
|
||||
void OnPointerPressed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((IButtonController)Element)?.SendPressed();
|
||||
}
|
||||
|
||||
void OnRadioButtonCheckedOrUnchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Element == null || Control == null)
|
||||
|
@ -178,8 +168,15 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
|
||||
void UpdateContent()
|
||||
{
|
||||
var text = Element.Text;
|
||||
Control.Content = text;
|
||||
var content = Element?.Content;
|
||||
|
||||
if (content is View view)
|
||||
{
|
||||
Control.Content = new WrapperControl(view);
|
||||
return;
|
||||
}
|
||||
|
||||
Control.Content = content?.ToString();
|
||||
}
|
||||
|
||||
void UpdateFont()
|
||||
|
@ -187,10 +184,12 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
if (Control == null || Element == null)
|
||||
return;
|
||||
|
||||
if (Element.Font == Font.Default && !_fontApplied)
|
||||
Font font = Font.OfSize(Element.FontFamily, Element.FontSize).WithAttributes(Element.FontAttributes);
|
||||
|
||||
if (font == Font.Default && !_fontApplied)
|
||||
return;
|
||||
|
||||
Font fontToApply = Element.Font == Font.Default ? Font.SystemFontOfSize(NamedSize.Medium) : Element.Font;
|
||||
Font fontToApply = font == Font.Default ? Font.SystemFontOfSize(NamedSize.Medium) : font;
|
||||
|
||||
Control.ApplyFont(fontToApply);
|
||||
_fontApplied = true;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using WBrush = System.Windows.Media.Brush;
|
||||
using WThickness = System.Windows.Thickness;
|
||||
|
@ -59,7 +60,7 @@ namespace Xamarin.Forms.Platform.WPF
|
|||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
|
||||
if (e.PropertyName == RadioButton.TextProperty.PropertyName || e.PropertyName == Button.ImageSourceProperty.PropertyName)
|
||||
if (e.PropertyName == RadioButton.ContentProperty.PropertyName || e.PropertyName == Button.ImageSourceProperty.PropertyName)
|
||||
{
|
||||
UpdateContent();
|
||||
}
|
||||
|
@ -71,7 +72,9 @@ namespace Xamarin.Forms.Platform.WPF
|
|||
{
|
||||
UpdateTextColor();
|
||||
}
|
||||
else if (e.PropertyName == RadioButton.FontProperty.PropertyName)
|
||||
else if (e.PropertyName == RadioButton.FontFamilyProperty.PropertyName
|
||||
|| e.PropertyName == RadioButton.FontSizeProperty.PropertyName
|
||||
|| e.PropertyName == RadioButton.FontAttributesProperty.PropertyName )
|
||||
{
|
||||
UpdateFont();
|
||||
}
|
||||
|
@ -142,7 +145,7 @@ namespace Xamarin.Forms.Platform.WPF
|
|||
|
||||
void UpdateContent()
|
||||
{
|
||||
Control.Content = Element.Text;
|
||||
Control.Content = Element?.ContentAsString();
|
||||
}
|
||||
|
||||
void UpdateFont()
|
||||
|
@ -150,10 +153,12 @@ namespace Xamarin.Forms.Platform.WPF
|
|||
if (Control == null || Element == null)
|
||||
return;
|
||||
|
||||
if (Element.Font == Font.Default && !_fontApplied)
|
||||
Font font = Font.OfSize(Element.FontFamily, Element.FontSize).WithAttributes(Element.FontAttributes);
|
||||
|
||||
if (font == Font.Default && !_fontApplied)
|
||||
return;
|
||||
|
||||
Font fontToApply = Element.Font == Font.Default ? Font.SystemFontOfSize(NamedSize.Medium) : Element.Font;
|
||||
Font fontToApply = font == Font.Default ? Font.SystemFontOfSize(NamedSize.Medium) : font;
|
||||
|
||||
Control.ApplyFont(fontToApply);
|
||||
_fontApplied = true;
|
||||
|
|
|
@ -55,18 +55,23 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
ItemsSource?.Dispose();
|
||||
|
||||
CollectionView.Delegate = null;
|
||||
Delegator?.Dispose();
|
||||
|
||||
_emptyUIView?.Dispose();
|
||||
_emptyUIView = null;
|
||||
|
||||
_emptyViewFormsElement = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
ItemsViewLayout?.Dispose();
|
||||
CollectionView?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
|
|
@ -222,7 +222,18 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
|
||||
public static IVisualElementRenderer CreateRenderer(VisualElement element)
|
||||
{
|
||||
var renderer = Internals.Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element) ?? new DefaultRenderer();
|
||||
IVisualElementRenderer renderer = null;
|
||||
|
||||
if (element is TemplatedView tv && tv.ResolveControlTemplate() != null)
|
||||
{
|
||||
renderer = new DefaultRenderer();
|
||||
}
|
||||
|
||||
if (renderer == null)
|
||||
{
|
||||
renderer = Internals.Registrar.Registered.GetHandlerForObject<IVisualElementRenderer>(element) ?? new DefaultRenderer();
|
||||
}
|
||||
|
||||
renderer.SetElement(element);
|
||||
return renderer;
|
||||
}
|
||||
|
|
|
@ -1,307 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using CoreAnimation;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using SizeF = CoreGraphics.CGSize;
|
||||
|
||||
namespace Xamarin.Forms.Platform.iOS
|
||||
{
|
||||
public class RadioButtonRenderer : ViewRenderer<RadioButton, UIButton>
|
||||
{
|
||||
UIColor _buttonTextColorDefaultDisabled;
|
||||
UIColor _buttonTextColorDefaultHighlighted;
|
||||
UIColor _buttonTextColorDefaultNormal;
|
||||
bool _useLegacyColorManagement;
|
||||
bool _titleChanged;
|
||||
SizeF _titleSize;
|
||||
UIEdgeInsets _paddingDelta = new UIEdgeInsets();
|
||||
CALayer _radioButtonLayer;
|
||||
|
||||
// This looks like it should be a const under iOS Classic,
|
||||
// but that doesn't work under iOS
|
||||
// ReSharper disable once BuiltInTypeReferenceStyle
|
||||
// Under iOS Classic Resharper wants to suggest this use the built-in type ref
|
||||
// but under iOS that suggestion won't work
|
||||
readonly nfloat _minimumButtonHeight = 44; // Apple docs
|
||||
|
||||
static readonly UIControlState[] s_controlStates = { UIControlState.Normal, UIControlState.Highlighted, UIControlState.Disabled };
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
[Preserve(Conditional = true)]
|
||||
public RadioButtonRenderer() : base()
|
||||
{
|
||||
BorderElementManager.Init(this);
|
||||
}
|
||||
|
||||
public override SizeF SizeThatFits(SizeF size)
|
||||
{
|
||||
var result = base.SizeThatFits(size);
|
||||
|
||||
if (result.Height < _minimumButtonHeight)
|
||||
{
|
||||
result.Height = _minimumButtonHeight;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
if (Control != null)
|
||||
{
|
||||
Control.TouchUpInside -= OnButtonTouchUpInside;
|
||||
Control.TouchDown -= OnButtonTouchDown;
|
||||
BorderElementManager.Dispose(this);
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<RadioButton> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
if (Control == null)
|
||||
{
|
||||
SetNativeControl(CreateNativeControl());
|
||||
SetRadioBoxLayer(CreateRadioBoxLayer());
|
||||
Control.HorizontalAlignment = UIControlContentHorizontalAlignment.Left;
|
||||
|
||||
Debug.Assert(Control != null, "Control != null");
|
||||
|
||||
SetControlPropertiesFromProxy();
|
||||
|
||||
_useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
|
||||
|
||||
_buttonTextColorDefaultNormal = Control.TitleColor(UIControlState.Normal);
|
||||
_buttonTextColorDefaultHighlighted = Control.TitleColor(UIControlState.Highlighted);
|
||||
_buttonTextColorDefaultDisabled = Control.TitleColor(UIControlState.Disabled);
|
||||
|
||||
Control.TouchUpInside += OnButtonTouchUpInside;
|
||||
Control.TouchDown += OnButtonTouchDown;
|
||||
}
|
||||
|
||||
UpdateText();
|
||||
UpdateFont();
|
||||
UpdateTextColor();
|
||||
UpdatePadding();
|
||||
}
|
||||
}
|
||||
|
||||
protected override UIButton CreateNativeControl()
|
||||
{
|
||||
return new UIButton(UIButtonType.System);
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
|
||||
if (e.PropertyName == RadioButton.TextProperty.PropertyName)
|
||||
UpdateText();
|
||||
else if (e.PropertyName == RadioButton.TextColorProperty.PropertyName)
|
||||
UpdateTextColor();
|
||||
else if (e.PropertyName == RadioButton.FontProperty.PropertyName)
|
||||
UpdateFont();
|
||||
else if (e.PropertyName == RadioButton.PaddingProperty.PropertyName)
|
||||
UpdatePadding();
|
||||
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
|
||||
_radioButtonLayer.SetNeedsDisplay();
|
||||
else if (e.PropertyName == RadioButton.IsCheckedProperty.PropertyName)
|
||||
_radioButtonLayer.SetNeedsDisplay();
|
||||
}
|
||||
|
||||
protected override void SetAccessibilityLabel()
|
||||
{
|
||||
// If we have not specified an AccessibilityLabel and the AccessibilityLabel is currently bound to the Title,
|
||||
// exit this method so we don't set the AccessibilityLabel value and break the binding.
|
||||
// This may pose a problem for users who want to explicitly set the AccessibilityLabel to null, but this
|
||||
// will prevent us from inadvertently breaking UI Tests that are using Query.Marked to get the dynamic Title
|
||||
// of the Button.
|
||||
|
||||
var elemValue = (string)Element?.GetValue(AutomationProperties.NameProperty);
|
||||
if (string.IsNullOrWhiteSpace(elemValue) && Control?.AccessibilityLabel == Control?.Title(UIControlState.Normal))
|
||||
return;
|
||||
|
||||
base.SetAccessibilityLabel();
|
||||
}
|
||||
|
||||
protected virtual CALayer CreateRadioBoxLayer()
|
||||
{
|
||||
return new RadioButtonCALayer(Element, Control);
|
||||
}
|
||||
|
||||
public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
|
||||
{
|
||||
base.TraitCollectionDidChange(previousTraitCollection);
|
||||
// Make sure the control adheres to changes in UI theme
|
||||
if (Forms.IsiOS13OrNewer && previousTraitCollection?.UserInterfaceStyle != TraitCollection.UserInterfaceStyle)
|
||||
_radioButtonLayer.SetNeedsDisplay();
|
||||
}
|
||||
|
||||
void SetRadioBoxLayer(CALayer layer)
|
||||
{
|
||||
_radioButtonLayer = layer;
|
||||
Control.Layer.AddSublayer(_radioButtonLayer);
|
||||
_radioButtonLayer.SetNeedsLayout();
|
||||
_radioButtonLayer.SetNeedsDisplay();
|
||||
}
|
||||
|
||||
void SetControlPropertiesFromProxy()
|
||||
{
|
||||
foreach (UIControlState uiControlState in s_controlStates)
|
||||
{
|
||||
Control.SetTitleColor(UIButton.Appearance.TitleColor(uiControlState), uiControlState); // if new values are null, old values are preserved.
|
||||
Control.SetTitleShadowColor(UIButton.Appearance.TitleShadowColor(uiControlState), uiControlState);
|
||||
Control.SetBackgroundImage(UIButton.Appearance.BackgroundImageForState(uiControlState), uiControlState);
|
||||
}
|
||||
}
|
||||
|
||||
void OnButtonTouchUpInside(object sender, EventArgs eventArgs)
|
||||
{
|
||||
ButtonElementManager.OnButtonTouchUpInside(this.Element);
|
||||
|
||||
if (!Element.IsChecked)
|
||||
Element.IsChecked = !Element.IsChecked;
|
||||
|
||||
_radioButtonLayer.SetNeedsDisplay();
|
||||
}
|
||||
|
||||
void OnButtonTouchDown(object sender, EventArgs eventArgs)
|
||||
{
|
||||
ButtonElementManager.OnButtonTouchDown(this.Element);
|
||||
}
|
||||
|
||||
void UpdateFont()
|
||||
{
|
||||
Control.TitleLabel.Font = Element.ToUIFont();
|
||||
}
|
||||
|
||||
void UpdateText()
|
||||
{
|
||||
var newText = Element.Text;
|
||||
|
||||
if (Control.Title(UIControlState.Normal) != newText)
|
||||
{
|
||||
Control.SetTitle(Element.Text, UIControlState.Normal);
|
||||
_titleChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTextColor()
|
||||
{
|
||||
if (Element.TextColor == Color.Default)
|
||||
{
|
||||
Control.SetTitleColor(_buttonTextColorDefaultNormal, UIControlState.Normal);
|
||||
Control.SetTitleColor(_buttonTextColorDefaultHighlighted, UIControlState.Highlighted);
|
||||
Control.SetTitleColor(_buttonTextColorDefaultDisabled, UIControlState.Disabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
var color = Element.TextColor.ToUIColor();
|
||||
|
||||
Control.SetTitleColor(color, UIControlState.Normal);
|
||||
Control.SetTitleColor(color, UIControlState.Highlighted);
|
||||
Control.SetTitleColor(_useLegacyColorManagement ? _buttonTextColorDefaultDisabled : color, UIControlState.Disabled);
|
||||
|
||||
Control.TintColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdatePadding(UIButton button = null)
|
||||
{
|
||||
var uiElement = button ?? Control;
|
||||
if (uiElement == null)
|
||||
return;
|
||||
uiElement.ContentEdgeInsets = new UIEdgeInsets(
|
||||
(float)(Element.Padding.Top + _paddingDelta.Top),
|
||||
(float)(Element.Padding.Left + _paddingDelta.Left),
|
||||
(float)(Element.Padding.Bottom + _paddingDelta.Bottom),
|
||||
(float)(Element.Padding.Right + _paddingDelta.Right)
|
||||
);
|
||||
|
||||
// Make room for radio box
|
||||
var contentEdgeInsets = uiElement.ContentEdgeInsets;
|
||||
contentEdgeInsets.Left += _radioButtonLayer.Frame.Left + _radioButtonLayer.Frame.Width;
|
||||
uiElement.ContentEdgeInsets = contentEdgeInsets;
|
||||
}
|
||||
|
||||
void UpdateContentEdge(UIButton button, UIEdgeInsets? delta = null)
|
||||
{
|
||||
_paddingDelta = delta ?? new UIEdgeInsets();
|
||||
UpdatePadding(button);
|
||||
}
|
||||
|
||||
void ClearEdgeInsets(UIButton button)
|
||||
{
|
||||
if (button == null)
|
||||
return;
|
||||
|
||||
Control.TitleEdgeInsets = new UIEdgeInsets(0, 0, 0, 0);
|
||||
UpdateContentEdge(Control);
|
||||
}
|
||||
|
||||
void ComputeEdgeInsets(UIButton button, Button.ButtonContentLayout layout)
|
||||
{
|
||||
if (button?.ImageView?.Image == null || string.IsNullOrEmpty(button.TitleLabel?.Text))
|
||||
return;
|
||||
|
||||
var position = layout.Position;
|
||||
var spacing = (nfloat)(layout.Spacing / 2);
|
||||
|
||||
if (position == Button.ButtonContentLayout.ImagePosition.Left)
|
||||
{
|
||||
button.TitleEdgeInsets = new UIEdgeInsets(0, spacing, 0, -spacing);
|
||||
UpdateContentEdge(button, new UIEdgeInsets(0, 2 * spacing, 0, 2 * spacing));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_titleChanged)
|
||||
{
|
||||
var stringToMeasure = new NSString(button.TitleLabel.Text);
|
||||
UIStringAttributes attribs = new UIStringAttributes { Font = button.TitleLabel.Font };
|
||||
_titleSize = stringToMeasure.GetSizeUsingAttributes(attribs);
|
||||
_titleChanged = false;
|
||||
}
|
||||
|
||||
var labelWidth = _titleSize.Width;
|
||||
var imageWidth = button.ImageView.Image.Size.Width;
|
||||
|
||||
if (position == Button.ButtonContentLayout.ImagePosition.Right)
|
||||
{
|
||||
button.ImageEdgeInsets = new UIEdgeInsets(0, labelWidth + spacing, 0, -labelWidth - spacing);
|
||||
button.TitleEdgeInsets = new UIEdgeInsets(0, -imageWidth - spacing, 0, imageWidth + spacing);
|
||||
UpdateContentEdge(button, new UIEdgeInsets(0, 2 * spacing, 0, 2 * spacing));
|
||||
return;
|
||||
}
|
||||
|
||||
var imageVertOffset = (_titleSize.Height / 2);
|
||||
var titleVertOffset = (button.ImageView.Image.Size.Height / 2);
|
||||
|
||||
var edgeOffset = (float)Math.Min(imageVertOffset, titleVertOffset);
|
||||
|
||||
UpdateContentEdge(button, new UIEdgeInsets(edgeOffset, 0, edgeOffset, 0));
|
||||
|
||||
var horizontalImageOffset = labelWidth / 2;
|
||||
var horizontalTitleOffset = imageWidth / 2;
|
||||
|
||||
if (position == Button.ButtonContentLayout.ImagePosition.Bottom)
|
||||
{
|
||||
imageVertOffset = -imageVertOffset;
|
||||
titleVertOffset = -titleVertOffset;
|
||||
}
|
||||
|
||||
button.ImageEdgeInsets = new UIEdgeInsets(-imageVertOffset, horizontalImageOffset, imageVertOffset, -horizontalImageOffset);
|
||||
button.TitleEdgeInsets = new UIEdgeInsets(titleVertOffset, -horizontalTitleOffset, -titleVertOffset, horizontalTitleOffset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -238,8 +238,18 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
return;
|
||||
|
||||
TapoffView.UpdateBackground(_backdropBrush);
|
||||
|
||||
if (Brush.IsNullOrEmpty(_backdropBrush))
|
||||
TapoffView.BackgroundColor = ColorExtensions.BackgroundColor.ColorWithAlpha(0.5f);
|
||||
{
|
||||
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
|
||||
{
|
||||
TapoffView.BackgroundColor = UIColor.Clear;
|
||||
}
|
||||
else
|
||||
{
|
||||
TapoffView.BackgroundColor = ColorExtensions.BackgroundColor.ColorWithAlpha(0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddTapoffView()
|
||||
|
|
|
@ -133,11 +133,6 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
return new DesignerFlyoutRenderer(this);
|
||||
}
|
||||
|
||||
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
|
||||
{
|
||||
return new TabletShellFlyoutRenderer();
|
||||
}
|
||||
|
||||
return new ShellFlyoutRenderer()
|
||||
{
|
||||
FlyoutTransition = new SlideFlyoutTransition()
|
||||
|
|
|
@ -11,7 +11,13 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
if (behavior == FlyoutBehavior.Locked)
|
||||
openPercent = 1;
|
||||
|
||||
nfloat flyoutWidth = (nfloat)(Math.Min(bounds.Width, bounds.Height) * 0.8);
|
||||
nfloat flyoutWidth;
|
||||
|
||||
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
|
||||
flyoutWidth = 320;
|
||||
else
|
||||
flyoutWidth = (nfloat)(Math.Min(bounds.Width, bounds.Height) * 0.8);
|
||||
|
||||
nfloat openLimit = flyoutWidth;
|
||||
nfloat openPixels = openLimit * openPercent;
|
||||
|
||||
|
|
|
@ -192,7 +192,6 @@
|
|||
<Compile Include="Renderers\CheckBoxRendererBase.cs" />
|
||||
<Compile Include="Renderers\PhoneFlyoutPageRenderer.cs" />
|
||||
<Compile Include="Renderers\RadioButtonCALayer.cs" />
|
||||
<Compile Include="Renderers\RadioButtonRenderer.cs" />
|
||||
<Compile Include="Renderers\TabletFlyoutPageRenderer.cs" />
|
||||
<Compile Include="Renderers\WkWebViewRenderer.cs" />
|
||||
<Compile Include="Renderers\ElementSelectedEventArgs.cs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче