Merge branch '5.0.0' into main

This commit is contained in:
Samantha Houts 2020-09-18 16:59:59 -07:00
Родитель 09d7101485 d79d5b77e7
Коммит 6d9a4b0d80
79 изменённых файлов: 3058 добавлений и 847 удалений

Просмотреть файл

@ -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" />

Просмотреть файл