Browse Source

Merge branch 'master' into default-members-2

pull/10678/head
Steven Kirk 3 years ago
committed by GitHub
parent
commit
967f899ab8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject
  2. 5
      .ncrunch/Generators.Sandbox.v3.ncrunchproject
  3. 2
      samples/ControlCatalog/MainView.xaml
  4. 2
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  5. 2
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  6. 2
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
  7. 4
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  8. 2
      samples/ControlCatalog/Pages/CursorPage.xaml
  9. 12
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  10. 2
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  11. 6
      samples/ControlCatalog/Pages/MenuPage.xaml
  12. 8
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs
  13. 6
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  14. 2
      samples/ControlCatalog/Pages/RefreshContainerPage.axaml
  15. 4
      samples/ControlCatalog/Pages/ScrollSnapPage.xaml
  16. 4
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  17. 2
      samples/ControlCatalog/Pages/TabStripPage.xaml
  18. 2
      samples/ControlCatalog/Pages/ThemePage.axaml.cs
  19. 2
      samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
  20. 2
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  21. 2
      samples/IntegrationTestApp/MainWindow.axaml
  22. 2
      src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs
  23. 2
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  24. 14
      src/Avalonia.Base/StyledProperty.cs
  25. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  26. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  27. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  28. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  29. 5
      src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
  30. 12
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
  31. 3
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  32. 8
      src/Avalonia.Controls/Documents/InlineCollection.cs
  33. 4
      src/Avalonia.Controls/Flyouts/MenuFlyout.cs
  34. 165
      src/Avalonia.Controls/ItemCollection.cs
  35. 212
      src/Avalonia.Controls/ItemsControl.cs
  36. 155
      src/Avalonia.Controls/ItemsSourceView.cs
  37. 135
      src/Avalonia.Controls/MaskedTextBox.cs
  38. 15
      src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs
  39. 109
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  40. 55
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  41. 105
      src/Avalonia.Controls/SelectableTextBlock.cs
  42. 27
      src/Avalonia.Controls/Selection/InternalSelectionModel.cs
  43. 4
      src/Avalonia.Controls/Selection/SelectionModel.cs
  44. 66
      src/Avalonia.Controls/TextBlock.cs
  45. 433
      src/Avalonia.Controls/TextBox.cs
  46. 19
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  47. 4
      src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
  48. 4
      src/Avalonia.Controls/Utils/UndoRedoHelper.cs
  49. 20
      src/Avalonia.Controls/VirtualizingPanel.cs
  50. 29
      src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs
  51. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml
  52. 6
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  53. 6
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  54. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs
  55. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
  56. 4
      src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml
  57. 6
      src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml
  58. 4
      src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
  59. 2
      src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml
  60. 6
      src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
  61. 4
      src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
  62. 43
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  63. 2
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  64. 2
      src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs
  65. 55
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  66. 20
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  67. 88
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  68. 225
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  69. 39
      tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs
  70. 43
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  71. 6
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
  72. 24
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  73. 10
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  74. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  75. 4
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  76. 166
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  77. 12
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs
  78. 231
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  79. 26
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
  80. 94
      tests/Avalonia.Controls.UnitTests/Primitives/TabStripTests.cs
  81. 220
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  82. 2
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
  83. 118
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  84. 4
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  85. 2
      tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs
  86. 15
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
  87. 14
      tests/Avalonia.LeakTests/ControlTests.cs
  88. 8
      tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs
  89. 13
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  90. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  91. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  92. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  93. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  94. 2
      tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs

5
.ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Generators.Sandbox.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

2
samples/ControlCatalog/MainView.xaml

@ -241,7 +241,7 @@
</ComboBox.Items>
</ComboBox>
<ComboBox HorizontalAlignment="Stretch"
Items="{Binding WindowStates}"
ItemsSource="{Binding WindowStates}"
SelectedItem="{Binding WindowState}" />
</StackPanel>
</Flyout>

2
samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
{
AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Get<ComboBox>("fontComboBox");
fontComboBox.Items = FontManager.Current.SystemFonts;
fontComboBox.ItemsSource = FontManager.Current.SystemFonts;
fontComboBox.SelectedIndex = 0;
}
}

2
samples/ControlCatalog/Pages/CompositionPage.axaml.cs

@ -32,7 +32,7 @@ public partial class CompositionPage : UserControl
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
this.Get<ItemsControl>("Items").Items = CreateColorItems();
this.Get<ItemsControl>("Items").ItemsSource = CreateColorItems();
}

2
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml

@ -61,7 +61,7 @@
<Border.Styles>
<Style Selector="MenuFlyoutPresenter MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>

4
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -51,13 +51,13 @@
<Border.Styles>
<Style Selector="ContextMenu MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</Border.Styles>
<Border.ContextMenu>
<ContextMenu Items="{Binding MenuItems}" />
<ContextMenu ItemsSource="{Binding MenuItems}" />
</Border.ContextMenu>
<TextBlock Text="Dynamically Generated"/>
</Border>

2
samples/ControlCatalog/Pages/CursorPage.xaml

@ -8,7 +8,7 @@
<TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
</StackPanel>
<ListBox Grid.Row="1" Items="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox Grid.Row="1" ItemsSource="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Cursor" Value="{Binding Cursor}" x:DataType="viewModels:StandardCursorModel"/>

12
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -106,7 +106,7 @@ namespace ControlCatalog.Pages
Directory = initialDirectory,
InitialFileName = initialFileName
}.ShowAsync(GetWindow());
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("OpenMultipleFiles").Click += async delegate
@ -118,7 +118,7 @@ namespace ControlCatalog.Pages
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
AllowMultiple = true
}.ShowAsync(GetWindow());
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("SaveFile").Click += async delegate
@ -132,7 +132,7 @@ namespace ControlCatalog.Pages
DefaultExtension = filters?.Any() == true ? "txt" : null,
InitialFileName = "test.txt"
}.ShowAsync(GetWindow());
results.Items = new[] { result };
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = result != null;
};
this.Get<Button>("SelectFolder").Click += async delegate
@ -149,7 +149,7 @@ namespace ControlCatalog.Pages
else
{
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
results.Items = new[] { result };
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = true;
}
};
@ -164,7 +164,7 @@ namespace ControlCatalog.Pages
{
AllowDirectorySelection = true
});
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("DecoratedWindow").Click += delegate
@ -332,7 +332,7 @@ namespace ControlCatalog.Pages
}
}
results.Items = mappedResults;
results.ItemsSource = mappedResults;
resultsVisible.IsVisible = mappedResults.Any();
}
}

2
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -30,7 +30,7 @@
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<Button Command="{Binding SelectRandomItemCommand}">Select Random Item</Button>
</StackPanel>
<ListBox Items="{Binding Items}"
<ListBox ItemsSource="{Binding Items}"
Selection="{Binding Selection}"
DisplayMemberBinding="{Binding (viewModels:ItemModel).ID, StringFormat='{}Item {0:N0}'}"
AutoScrollToSelectedItem="{Binding AutoScrollToSelectedItem}"

6
samples/ControlCatalog/Pages/MenuPage.xaml

@ -45,11 +45,11 @@
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
<Menu Items="{Binding MenuItems}">
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Styles>
<Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
@ -68,7 +68,7 @@
<Separator/>
<MenuItem Header="Execu_te Script..." />
<Separator/>
<MenuItem Header="_Recent" Items="{Binding RecentItems}">
<MenuItem Header="_Recent" ItemsSource="{Binding RecentItems}">
<MenuItem.Styles>
<Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>

8
samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs

@ -33,10 +33,10 @@ namespace ControlCatalog.Pages
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
Items =
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}

6
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -27,7 +27,7 @@
</Grid>
<Grid Grid.Row="0" Grid.Column="1" Margin="8" ColumnDefinitions="Auto, 120" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
<ComboBox Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center" Margin="2">
<ComboBox.ItemTemplate>
<DataTemplate>
@ -41,11 +41,11 @@
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock>
<ComboBox Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
<ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}"
<ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Cultures}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>

2
samples/ControlCatalog/Pages/RefreshContainerPage.axaml

@ -21,7 +21,7 @@
Margin="5">
<ListBox HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Items="{Binding Items}"/>
ItemsSource="{Binding Items}"/>
</RefreshContainer>
</DockPanel>
</UserControl>

4
samples/ControlCatalog/Pages/ScrollSnapPage.xaml

@ -16,14 +16,14 @@
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Type" />
<ComboBox Items="{Binding AvailableSnapPointsType}"
<ComboBox ItemsSource="{Binding AvailableSnapPointsType}"
SelectedItem="{Binding SnapPointsType}" />
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Alignment" />
<ComboBox Items="{Binding AvailableSnapPointsAlignment}"
<ComboBox ItemsSource="{Binding AvailableSnapPointsAlignment}"
SelectedItem="{Binding SnapPointsAlignment}" />
</StackPanel>

4
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@ -13,12 +13,12 @@
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Horizontal Scroll" />
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" />
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Vertical Scroll" />
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" />
</StackPanel>
</StackPanel>

2
samples/ControlCatalog/Pages/TabStripPage.xaml

@ -18,7 +18,7 @@
<Separator Margin="0 16"/>
<TextBlock Classes="h1">Dynamically generated</TextBlock>
<TabStrip Items="{Binding Tabs}">
<TabStrip ItemsSource="{Binding Tabs}">
<TabStrip.Styles>
<Style Selector="TabStripItem" x:DataType="viewModels:TabControlPageViewModelItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>

2
samples/ControlCatalog/Pages/ThemePage.axaml.cs

@ -12,7 +12,7 @@ namespace ControlCatalog.Pages
{
InitializeComponent();
Selector.Items = new[]
Selector.ItemsSource = new[]
{
ThemeVariant.Default,
ThemeVariant.Dark,

2
samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml

@ -53,7 +53,7 @@
<StackPanel Margin="5" Spacing="5" Grid.IsSharedSizeScope="True">
<HeaderedContentControl Header="Select a transition">
<ComboBox Items="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" />
<ComboBox ItemsSource="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" />
</HeaderedContentControl>
<HeaderedContentControl Header="Duration">
<NumericUpDown Value="{Binding Duration}" Increment="250" Minimum="100" />

2
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -11,7 +11,7 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TreeView Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>

2
samples/IntegrationTestApp/MainWindow.axaml

@ -109,7 +109,7 @@
<StackPanel DockPanel.Dock="Bottom">
<Button Name="ListBoxSelectionClear">Clear Selection</Button>
</StackPanel>
<ListBox Name="BasicListBox" Items="{Binding ListBoxItems}" SelectionMode="Multiple"/>
<ListBox Name="BasicListBox" ItemsSource="{Binding ListBoxItems}" SelectionMode="Multiple"/>
</DockPanel>
</TabItem>

2
src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs

@ -9,7 +9,7 @@ namespace Avalonia.Metadata;
/// A typical usage example is a ListBox control, where <see cref="InheritDataTypeFromItemsAttribute"/> is defined on the ItemTemplate property,
/// allowing the template to inherit the data type from the Items collection binding.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class InheritDataTypeFromItemsAttribute : Attribute
{
/// <summary>

2
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -31,7 +31,7 @@ namespace Avalonia.PropertyStore
var value = inherited is null ? _metadata.DefaultValue : inherited.Value;
if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
if (_metadata.CoerceValue is { } coerce)
{
_uncommon = new()
{

14
src/Avalonia.Base/StyledProperty.cs

@ -34,7 +34,6 @@ namespace Avalonia
{
Inherits = inherits;
ValidateValue = validate;
HasCoercion |= metadata.CoerceValue != null;
if (validate?.Invoke(metadata.DefaultValue) == false)
{
@ -48,12 +47,6 @@ namespace Avalonia
/// </summary>
public Func<TValue, bool>? ValidateValue { get; }
/// <summary>
/// Gets a value indicating whether this property has any value coercion callbacks defined
/// in its metadata.
/// </summary>
internal bool HasCoercion { get; private set; }
/// <summary>
/// Registers the property on another type.
/// </summary>
@ -130,10 +123,7 @@ namespace Avalonia
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject
{
base.OverrideMetadata(typeof(T), metadata);
}
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject => OverrideMetadata(typeof(T), metadata);
/// <summary>
/// Overrides the metadata for the property on the specified type.
@ -151,8 +141,6 @@ namespace Avalonia
}
}
HasCoercion |= metadata.CoerceValue != null;
base.OverrideMetadata(type, metadata);
}

2
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -165,7 +165,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -414,7 +414,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -165,7 +165,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@ -376,7 +376,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

5
src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs

@ -39,7 +39,10 @@ namespace Avalonia.Controls
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsRepeater, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsRepeater>(o => o.Items, (o, v) => o.Items = v);
AvaloniaProperty.RegisterDirect<ItemsRepeater, IEnumerable?>(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="Layout"/> property.

12
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs

@ -87,12 +87,10 @@ namespace Avalonia.Controls
/// Identifies the <see cref="Text" /> property.
/// </summary>
/// <value>The identifier for the <see cref="Text" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<AutoCompleteBox>(
o => o.Text,
(o, v) => o.Text = v,
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<AutoCompleteBox>(new(string.Empty,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
enableDataValidation: true));
/// <summary>
/// Identifies the <see cref="SearchText" /> property.
@ -317,8 +315,8 @@ namespace Avalonia.Controls
/// <see cref="AutoCompleteBox" /> control.</value>
public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>

3
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -198,7 +198,6 @@ namespace Avalonia.Controls
private bool _isDropDownOpen;
private bool _isFocused = false;
private string? _text = string.Empty;
private string? _searchText = string.Empty;
private AutoCompleteFilterPredicate<object?>? _itemFilter;
@ -1275,7 +1274,7 @@ namespace Avalonia.Controls
if ((userInitiated ?? true) && Text != value)
{
_ignoreTextPropertyChange++;
Text = value;
SetCurrentValue(TextProperty, value);
callTextChanged = true;
}

8
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -91,11 +91,11 @@ namespace Avalonia.Controls.Documents
public override void Add(Inline inline)
{
if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text))
if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock.Text))
{
base.Add(new Run(textBlock._text));
base.Add(new Run(textBlock.Text));
textBlock._text = null;
textBlock.ClearTextInternal();
}
base.Add(inline);
@ -113,7 +113,7 @@ namespace Avalonia.Controls.Documents
{
if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent)
{
textBlock._text += text;
textBlock.Text += text;
}
else
{

4
src/Avalonia.Controls/Flyouts/MenuFlyout.cs

@ -19,7 +19,9 @@ namespace Avalonia.Controls
/// Defines the <see cref="Items"/> property
/// </summary>
public static readonly DirectProperty<MenuFlyout, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<MenuFlyout>(x => x.Items,
AvaloniaProperty.RegisterDirect<MenuFlyout, IEnumerable?>(
nameof(Items),
x => x.Items,
(x, v) => x.Items = v);
/// <summary>

165
src/Avalonia.Controls/ItemCollection.cs

@ -0,0 +1,165 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Collections;
namespace Avalonia.Controls
{
/// <summary>
/// Holds the list of items that constitute the content of an <see cref="ItemsControl"/>.
/// </summary>
public class ItemCollection : ItemsSourceView, IList
{
// Suppress "Avoid zero-length array allocations": This is a sentinel value and must be unique.
#pragma warning disable CA1825
private static readonly object?[] s_uninitialized = new object?[0];
#pragma warning restore CA1825
private Mode _mode;
internal ItemCollection()
: base(s_uninitialized)
{
}
public new object? this[int index]
{
get => base[index];
set => WritableSource[index] = value;
}
public bool IsReadOnly => _mode == Mode.ItemsSource;
internal event EventHandler? SourceChanged;
/// <summary>
/// Adds an item to the <see cref="ItemsControl"/>.
/// </summary>
/// <param name="value">The item to add to the collection.</param>
/// <returns>
/// The position into which the new element was inserted, or -1 to indicate that
/// the item was not inserted into the collection.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public int Add(object? value) => WritableSource.Add(value);
/// <summary>
/// Clears the collection and releases the references on all items currently in the
/// collection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void Clear() => WritableSource.Clear();
/// <summary>
/// Inserts an element into the collection at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which to insert the item.</param>
/// <param name="value">The item to insert.</param>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void Insert(int index, object? value) => WritableSource.Insert(index, value);
/// <summary>
/// Removes the item at the specified index of the collection or view.
/// </summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void RemoveAt(int index) => WritableSource.RemoveAt(index);
/// <summary>
/// Removes the specified item reference from the collection or view.
/// </summary>
/// <param name="value">The object to remove.</param>
/// <returns>True if the item was removed; otherwise false.</returns>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public bool Remove(object? value)
{
var c = Count;
WritableSource.Remove(value);
return Count < c;
}
int IList.Add(object? value) => Add(value);
void IList.Clear() => Clear();
void IList.Insert(int index, object? value) => Insert(index, value);
void IList.RemoveAt(int index) => RemoveAt(index);
private IList WritableSource
{
get
{
if (IsReadOnly)
ThrowIsItemsSource();
if (Source == s_uninitialized)
SetSource(CreateDefaultCollection());
return Source;
}
}
internal IList? GetItemsPropertyValue()
{
if (_mode == Mode.ObsoleteItemsSetter)
return Source == s_uninitialized ? null : Source;
return this;
}
internal void SetItems(IList? items)
{
_mode = Mode.ObsoleteItemsSetter;
SetSource(items ?? s_uninitialized);
}
internal void SetItemsSource(IEnumerable? value)
{
if (_mode != Mode.ItemsSource && Count > 0)
throw new InvalidOperationException(
"Items collection must be empty before using ItemsSource.");
_mode = value is not null ? Mode.ItemsSource : Mode.Items;
SetSource(value ?? CreateDefaultCollection());
}
private new void SetSource(IEnumerable source)
{
var oldSource = Source;
base.SetSource(source);
if (oldSource.Count > 0)
RaiseCollectionChanged(new(NotifyCollectionChangedAction.Remove, oldSource, 0));
if (Source.Count > 0)
RaiseCollectionChanged(new(NotifyCollectionChangedAction.Add, Source, 0));
SourceChanged?.Invoke(this, EventArgs.Empty);
}
private static AvaloniaList<object?> CreateDefaultCollection()
{
return new() { ResetBehavior = ResetBehavior.Remove };
}
[DoesNotReturn]
private static void ThrowIsItemsSource()
{
throw new InvalidOperationException(
"Operation is not valid while ItemsSource is in use." +
"Access and modify elements with ItemsControl.ItemsSource instead.");
}
private enum Mode
{
Items,
ItemsSource,
ObsoleteItemsSetter,
}
}
}

212
src/Avalonia.Controls/ItemsControl.cs

@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@ -34,8 +35,13 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsControl, IEnumerable?> ItemsProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, IEnumerable?>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
public static readonly DirectProperty<ItemsControl, IList?> ItemsProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, IList?>(
nameof(Items),
o => o.Items,
#pragma warning disable CS0618 // Type or member is obsolete
(o, v) => o.Items = v);
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Defines the <see cref="ItemContainerTheme"/> property.
@ -56,23 +62,23 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel);
/// <summary>
/// Defines the <see cref="ItemTemplate"/> property.
/// Defines the <see cref="ItemsSource"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
AvaloniaProperty.Register<ItemsControl, IEnumerable?>(nameof(ItemsSource));
/// <summary>
/// Defines the <see cref="ItemsView"/> property.
/// Defines the <see cref="ItemTemplate"/> property.
/// </summary>
public static readonly DirectProperty<ItemsControl, ItemsSourceView> ItemsViewProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, ItemsSourceView>(nameof(ItemsView), o => o.ItemsView);
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
/// <summary>
/// Defines the <see cref="DisplayMemberBinding" /> property
/// </summary>
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
/// <summary>
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
@ -89,15 +95,15 @@ namespace Avalonia.Controls
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding
{
get => GetValue(DisplayMemberBindingProperty);
set => SetValue(DisplayMemberBindingProperty, value);
}
private IEnumerable? _items = new AvaloniaList<object>();
private ItemsSourceView _itemsView;
private readonly ItemCollection _items = new();
private int _itemCount;
private ItemContainerGenerator? _itemContainerGenerator;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
@ -110,9 +116,8 @@ namespace Avalonia.Controls
/// </summary>
public ItemsControl()
{
_itemsView = ItemsSourceView.GetOrCreate(_items);
_itemsView.PostCollectionChanged += ItemsCollectionChanged;
UpdatePseudoClasses(0);
UpdatePseudoClasses();
_items.CollectionChanged += OnItemsViewCollectionChanged;
}
/// <summary>
@ -128,11 +133,45 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the items to display.
/// </summary>
/// <remarks>
/// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
/// and an <see cref="ItemsSource"/> property. The properties have the following differences:
///
/// <list type="bullet">
/// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
/// meaning that it cannot be styled </item>
/// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
/// is marked as the content property and will be used for items added via inline XAML.</item>
/// </list>
///
/// In Avalonia 11 the two properties can be used almost interchangeably but this will change
/// in a later version. In order to be ready for this change, follow the following guidance:
///
/// <list type="bullet">
/// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
/// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
/// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
/// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
/// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
/// binding a collection of models which will be transformed by a data template.</item>
/// </list>
/// </remarks>
[Content]
public IEnumerable? Items
public IList? Items
{
get => _items;
set => SetAndRaise(ItemsProperty, ref _items, value);
get => _items.GetItemsPropertyValue();
[Obsolete("Use ItemsSource to set or bind items.")]
set
{
var oldItems = _items.GetItemsPropertyValue();
if (value != oldItems)
{
_items.SetItems(value);
RaisePropertyChanged(ItemsProperty, oldItems, value);
}
}
}
/// <summary>
@ -140,17 +179,24 @@ namespace Avalonia.Controls
/// </summary>
public ControlTheme? ItemContainerTheme
{
get => GetValue(ItemContainerThemeProperty);
get => GetValue(ItemContainerThemeProperty);
set => SetValue(ItemContainerThemeProperty, value);
}
/// <summary>
/// Gets the number of items in <see cref="Items"/>.
/// Gets the number of items being displayed by the <see cref="ItemsControl"/>.
/// </summary>
public int ItemCount
{
get => _itemCount;
private set => SetAndRaise(ItemCountProperty, ref _itemCount, value);
private set
{
if (SetAndRaise(ItemCountProperty, ref _itemCount, value))
{
UpdatePseudoClasses();
_childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.TotalCountChanged);
}
}
}
/// <summary>
@ -162,13 +208,46 @@ namespace Avalonia.Controls
set => SetValue(ItemsPanelProperty, value);
}
/// <summary>
/// Gets or sets a collection used to generate the content of the <see cref="ItemsControl"/>.
/// </summary>
/// <remarks>
/// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
/// and an <see cref="ItemsSource"/> property. The properties have the following differences:
///
/// <list type="bullet">
/// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
/// meaning that it cannot be styled </item>
/// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
/// is marked as the content property and will be used for items added via inline XAML.</item>
/// </list>
///
/// In Avalonia 11 the two properties can be used almost interchangeably but this will change
/// in a later version. In order to be ready for this change, follow the following guidance:
///
/// <list type="bullet">
/// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
/// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
/// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
/// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
/// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
/// binding a collection of models which will be transformed by a data template.</item>
/// </list>
/// </remarks>
public IEnumerable? ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
/// <summary>
/// Gets or sets the data template used to display the items in the control.
/// </summary>
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
get => GetValue(ItemTemplateProperty);
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
@ -183,31 +262,9 @@ namespace Avalonia.Controls
public Panel? ItemsPanelRoot => Presenter?.Panel;
/// <summary>
/// Gets a standardized view over <see cref="Items"/>.
/// Gets a read-only view of the items in the <see cref="ItemsControl"/>.
/// </summary>
/// <remarks>
/// The <see cref="Items"/> property may be an enumerable which does not implement
/// <see cref="IList"/> or may be null. This view can be used to provide a standardized
/// view of the current items regardless of the type of the concrete collection, and
/// without having to deal with null values.
/// </remarks>
public ItemsSourceView ItemsView
{
get => _itemsView;
private set
{
if (ReferenceEquals(_itemsView, value))
return;
var oldValue = _itemsView;
RemoveControlItemsFromLogicalChildren(_itemsView);
_itemsView.PostCollectionChanged -= ItemsCollectionChanged;
_itemsView = value;
_itemsView.PostCollectionChanged += ItemsCollectionChanged;
AddControlItemsToLogicalChildren(_itemsView);
RaisePropertyChanged(ItemsViewProperty, oldValue, _itemsView);
}
}
public ItemsSourceView ItemsView => _items;
private protected bool WrapFocus { get; set; }
@ -262,7 +319,7 @@ namespace Avalonia.Controls
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get => GetValue(AreHorizontalSnapPointsRegularProperty);
get => GetValue(AreHorizontalSnapPointsRegularProperty);
set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
}
@ -271,7 +328,7 @@ namespace Avalonia.Controls
/// </summary>
public bool AreVerticalSnapPointsRegular
{
get => GetValue(AreVerticalSnapPointsRegularProperty);
get => GetValue(AreVerticalSnapPointsRegularProperty);
set => SetValue(AreVerticalSnapPointsRegularProperty, value);
}
@ -295,7 +352,7 @@ namespace Avalonia.Controls
/// </returns>
public Control? ContainerFromItem(object item)
{
var index = ItemsView.IndexOf(item);
var index = _items.IndexOf(item);
return index >= 0 ? ContainerFromIndex(index) : null;
}
@ -319,7 +376,7 @@ namespace Avalonia.Controls
public object? ItemFromContainer(Control container)
{
var index = IndexFromContainer(container);
return index >= 0 && index < ItemsView.Count ? ItemsView[index] : null;
return index >= 0 && index < _items.Count ? _items[index] : null;
}
/// <summary>
@ -478,19 +535,13 @@ namespace Avalonia.Controls
{
base.OnPropertyChanged(change);
if (change.Property == ItemsProperty)
{
ItemsView = ItemsSourceView.GetOrCreate(change.GetNewValue<IEnumerable?>());
ItemCount = ItemsView.Count;
}
else if (change.Property == ItemCountProperty)
if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
{
UpdatePseudoClasses(change.GetNewValue<int>());
_childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.TotalCountChanged);
RefreshContainers();
}
else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
else if (change.Property == ItemsSourceProperty)
{
RefreshContainers();
_items.SetItemsSource(change.GetNewValue<IEnumerable?>());
}
else if (change.Property == ItemTemplateProperty)
{
@ -517,24 +568,27 @@ namespace Avalonia.Controls
/// <summary>
/// Called when the <see cref="INotifyCollectionChanged.CollectionChanged"/> event is
/// raised on <see cref="Items"/>.
/// raised on <see cref="ItemsView"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected virtual void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
private protected virtual void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
ItemCount = _itemsView.Count;
switch (e.Action)
if (!_items.IsReadOnly)
{
case NotifyCollectionChangedAction.Add:
AddControlItemsToLogicalChildren(e.NewItems);
break;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddControlItemsToLogicalChildren(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
}
ItemCount = ItemsView.Count;
}
/// <summary>
@ -578,7 +632,7 @@ namespace Avalonia.Controls
{
var itemContainerTheme = ItemContainerTheme;
if (itemContainerTheme is not null &&
if (itemContainerTheme is not null &&
!container.IsSet(ThemeProperty) &&
((IStyleable)container).StyleKey == itemContainerTheme.TargetType)
{
@ -609,10 +663,6 @@ namespace Avalonia.Controls
ClearContainerForItemOverride(container);
}
/// <summary>
/// Given a collection of items, adds those that are controls to the logical children.
/// </summary>
/// <param name="items">The items.</param>
private void AddControlItemsToLogicalChildren(IEnumerable? items)
{
if (items is null)
@ -633,10 +683,6 @@ namespace Avalonia.Controls
LogicalChildren.AddRange(toAdd);
}
/// <summary>
/// Given a collection of items, removes those that are controls to from logical children.
/// </summary>
/// <param name="items">The items.</param>
private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
{
if (items is null)
@ -674,10 +720,10 @@ namespace Avalonia.Controls
return _displayMemberItemTemplate;
}
private void UpdatePseudoClasses(int itemCount)
private void UpdatePseudoClasses()
{
PseudoClasses.Set(":empty", itemCount == 0);
PseudoClasses.Set(":singleitem", itemCount == 1);
PseudoClasses.Set(":empty", ItemCount == 0);
PseudoClasses.Set(":singleitem", ItemCount == 1);
}
protected static IInputElement? GetNextControl(

155
src/Avalonia.Controls/ItemsSourceView.cs

@ -7,6 +7,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Controls.Utils;
@ -17,15 +18,16 @@ namespace Avalonia.Controls
/// and an items control.
/// </summary>
public class ItemsSourceView : IReadOnlyList<object?>,
IList,
INotifyCollectionChanged,
ICollectionChangedListener
{
/// <summary>
/// Gets an empty <see cref="ItemsSourceView"/>
/// Gets an empty <see cref="ItemsSourceView"/>
/// </summary>
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object>());
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object?>());
private readonly IList _inner;
private IList _source;
private NotifyCollectionChangedEventHandler? _collectionChanged;
private NotifyCollectionChangedEventHandler? _preCollectionChanged;
private NotifyCollectionChangedEventHandler? _postCollectionChanged;
@ -35,30 +37,17 @@ namespace Avalonia.Controls
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
/// </summary>
/// <param name="source">The data source.</param>
private protected ItemsSourceView(IEnumerable source)
{
_inner = source switch
{
ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged => throw new ArgumentException(
"Collection implements INotifyCollectionChanged but not IList.",
nameof(source)),
IEnumerable<object> iObj => new List<object>(iObj),
null => throw new ArgumentNullException(nameof(source)),
_ => new List<object>(source.Cast<object>())
};
}
private protected ItemsSourceView(IEnumerable source) => SetSource(source);
/// <summary>
/// Gets the number of items in the collection.
/// </summary>
public int Count => Inner.Count;
public int Count => Source.Count;
/// <summary>
/// Gets the inner collection.
/// Gets the source collection.
/// </summary>
public IList Inner => _inner;
public IList Source => _source;
/// <summary>
/// Retrieves the item at the specified index.
@ -67,12 +56,20 @@ namespace Avalonia.Controls
/// <returns>The item.</returns>
public object? this[int index] => GetAt(index);
bool IList.IsFixedSize => false;
bool IList.IsReadOnly => true;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
object? IList.this[int index]
{
get => GetAt(index);
set => ThrowReadOnly();
}
/// <summary>
/// Gets a value that indicates whether the items source can provide a unique key for each item.
/// </summary>
/// <remarks>
/// Not implemented in Avalonia, preserved here for ItemsRepeater's usage.
/// </remarks>
/// </summary>
internal bool HasKeyIndexMapping => false;
/// <summary>
@ -131,39 +128,14 @@ namespace Avalonia.Controls
}
}
private void AddListenerIfNecessary()
{
if (!_listening)
{
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.AddListener(incc, this);
_listening = true;
}
}
private void RemoveListenerIfNecessary()
{
if (_listening && _collectionChanged is null && _postCollectionChanged is null)
{
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.RemoveListener(incc, this);
_listening = false;
}
}
/// <summary>
/// Retrieves the item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public object? GetAt(int index) => Inner[index];
/// <summary>
/// Determines the index of a specific item in the collection.
/// </summary>
/// <param name="item">The object to locate in the collection.</param>
/// <returns>The index of value if found in the list; otherwise, -1.</returns>
public int IndexOf(object? item) => Inner.IndexOf(item);
public object? GetAt(int index) => Source[index];
public bool Contains(object? item) => Source.Contains(item);
public int IndexOf(object? item) => Source.IndexOf(item);
/// <summary>
/// Gets or creates an <see cref="ItemsSourceView"/> for the specified enumerable.
@ -201,7 +173,8 @@ namespace Avalonia.Controls
{
return items switch
{
ItemsSourceView<T> isv => isv,
ItemsSourceView<T> isvt => isvt,
ItemsSourceView isv => new ItemsSourceView<T>(isv.Source),
null => ItemsSourceView<T>.Empty,
_ => new ItemsSourceView<T>(items)
};
@ -236,7 +209,7 @@ namespace Avalonia.Controls
yield return o;
}
var inner = Inner;
var inner = Source;
return inner switch
{
@ -245,7 +218,7 @@ namespace Avalonia.Controls
};
}
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Source.GetEnumerator();
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
@ -262,15 +235,69 @@ namespace Avalonia.Controls
_postCollectionChanged?.Invoke(this, e);
}
int IList.Add(object? value) => ThrowReadOnly();
void IList.Clear() => ThrowReadOnly();
void IList.Insert(int index, object? value) => ThrowReadOnly();
void IList.Remove(object? value) => ThrowReadOnly();
void IList.RemoveAt(int index) => ThrowReadOnly();
void ICollection.CopyTo(Array array, int index) => Source.CopyTo(array, index);
/// <summary>
/// Retrieves the index of the item that has the specified unique identifier (key).
/// Not implemented in Avalonia, preserved here for ItemsRepeater's usage.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The key</returns>
/// <remarks>
/// TODO: Not yet implemented in Avalonia.
/// </remarks>
internal string KeyFromIndex(int index) => throw new NotImplementedException();
private protected void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
_preCollectionChanged?.Invoke(this, e);
_collectionChanged?.Invoke(this, e);
_postCollectionChanged?.Invoke(this, e);
}
[MemberNotNull(nameof(_source))]
private protected void SetSource(IEnumerable source)
{
if (_listening && _source is INotifyCollectionChanged inccOld)
CollectionChangedEventManager.Instance.RemoveListener(inccOld, this);
_source = source switch
{
ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged => throw new ArgumentException(
"Collection implements INotifyCollectionChanged but not IList.",
nameof(source)),
IEnumerable<object> iObj => new List<object>(iObj),
null => throw new ArgumentNullException(nameof(source)),
_ => new List<object>(source.Cast<object>())
};
if (_listening && _source is INotifyCollectionChanged inccNew)
CollectionChangedEventManager.Instance.AddListener(inccNew, this);
}
private void AddListenerIfNecessary()
{
if (!_listening)
{
if (_source is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.AddListener(incc, this);
_listening = true;
}
}
private void RemoveListenerIfNecessary()
{
if (_listening && _collectionChanged is null && _postCollectionChanged is null)
{
if (_source is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.RemoveListener(incc, this);
_listening = false;
}
}
[DoesNotReturn]
private static int ThrowReadOnly() => throw new NotSupportedException("Collection is read-only.");
}
public sealed class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
@ -306,7 +333,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public new T GetAt(int index) => (T)Inner[index]!;
public new T GetAt(int index) => (T)Source[index]!;
public new IEnumerator<T> GetEnumerator()
{
@ -316,7 +343,7 @@ namespace Avalonia.Controls
yield return (T)o;
}
var inner = Inner;
var inner = Source;
return inner switch
{
@ -325,6 +352,6 @@ namespace Avalonia.Controls
};
}
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Source.GetEnumerator();
}
}

135
src/Avalonia.Controls/MaskedTextBox.cs

@ -15,9 +15,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> AsciiOnlyProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(AsciiOnly));
public static readonly DirectProperty<MaskedTextBox, CultureInfo?> CultureProperty =
AvaloniaProperty.RegisterDirect<MaskedTextBox, CultureInfo?>(nameof(Culture), o => o.Culture,
(o, v) => o.Culture = v, CultureInfo.CurrentCulture);
public static readonly StyledProperty<CultureInfo?> CultureProperty =
AvaloniaProperty.Register<MaskedTextBox, CultureInfo?>(nameof(Culture), CultureInfo.CurrentCulture);
public static readonly StyledProperty<bool> HidePromptOnLeaveProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(HidePromptOnLeave));
@ -32,26 +31,49 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty);
public static readonly StyledProperty<char> PromptCharProperty =
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_');
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_', coerce: CoercePromptChar);
public static readonly DirectProperty<MaskedTextBox, bool> ResetOnPromptProperty =
AvaloniaProperty.RegisterDirect<MaskedTextBox, bool>(nameof(ResetOnPrompt), o => o.ResetOnPrompt, (o, v) => o.ResetOnPrompt = v);
public static readonly StyledProperty<bool> ResetOnPromptProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(ResetOnPrompt), true);
public static readonly DirectProperty<MaskedTextBox, bool> ResetOnSpaceProperty =
AvaloniaProperty.RegisterDirect<MaskedTextBox, bool>(nameof(ResetOnSpace), o => o.ResetOnSpace, (o, v) => o.ResetOnSpace = v);
public static readonly StyledProperty<bool> ResetOnSpaceProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(ResetOnSpace), true);
private CultureInfo? _culture;
private bool _ignoreTextChanges;
private bool _resetOnPrompt = true;
static MaskedTextBox()
{
PasswordCharProperty.OverrideMetadata<MaskedTextBox>(new('\0', coerce: CoercePasswordChar));
}
private bool _ignoreTextChanges;
private static char CoercePasswordChar(AvaloniaObject sender, char baseValue)
{
if (!MaskedTextProvider.IsValidPasswordChar(baseValue))
{
throw new ArgumentException($"'{baseValue}' is not a valid value for PasswordChar.");
}
var textbox = (MaskedTextBox)sender;
if (textbox.MaskProvider is { } maskProvider && baseValue == maskProvider.PromptChar)
{
// Prompt and password chars must be different.
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
}
private bool _resetOnSpace = true;
return baseValue;
}
static MaskedTextBox()
private static char CoercePromptChar(AvaloniaObject sender, char baseValue)
{
PasswordCharProperty
.OverrideDefaultValue<MaskedTextBox>('\0');
if (!MaskedTextProvider.IsValidInputChar(baseValue))
{
throw new ArgumentException($"'{baseValue}' is not a valid value for PromptChar.");
}
if (baseValue == sender.GetValue(PasswordCharProperty))
{
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
}
return baseValue;
}
public MaskedTextBox() { }
@ -59,6 +81,9 @@ namespace Avalonia.Controls
/// <summary>
/// Constructs the MaskedTextBox with the specified MaskedTextProvider object.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty",
"AVP1012:An AvaloniaObject should use SetCurrentValue when assigning its own StyledProperty or AttachedProperty values",
Justification = "These values are being explicitly provided by a constructor parameter.")]
public MaskedTextBox(MaskedTextProvider maskedTextProvider)
{
if (maskedTextProvider == null)
@ -87,8 +112,8 @@ namespace Avalonia.Controls
/// </summary>
public CultureInfo? Culture
{
get => _culture;
set => SetAndRaise(CultureProperty, ref _culture, value);
get => GetValue(CultureProperty);
set => SetValue(CultureProperty, value);
}
/// <summary>
@ -131,15 +156,6 @@ namespace Avalonia.Controls
/// </summary>
public MaskedTextProvider? MaskProvider { get; private set; }
/// <summary>
/// Gets or sets the character to be displayed in substitute for user input.
/// </summary>
public new char PasswordChar
{
get => GetValue(PasswordCharProperty);
set => SetValue(PasswordCharProperty, value);
}
/// <summary>
/// Gets or sets the character used to represent the absence of user input in MaskedTextBox.
/// </summary>
@ -154,16 +170,8 @@ namespace Avalonia.Controls
/// </summary>
public bool ResetOnPrompt
{
get => _resetOnPrompt;
set
{
SetAndRaise(ResetOnPromptProperty, ref _resetOnPrompt, value);
if (MaskProvider != null)
{
MaskProvider.ResetOnPrompt = value;
}
}
get => GetValue(ResetOnPromptProperty);
set => SetValue(ResetOnPromptProperty, value);
}
/// <summary>
@ -171,16 +179,8 @@ namespace Avalonia.Controls
/// </summary>
public bool ResetOnSpace
{
get => _resetOnSpace;
set
{
SetAndRaise(ResetOnSpaceProperty, ref _resetOnSpace, value);
if (MaskProvider != null)
{
MaskProvider.ResetOnSpace = value;
}
}
get => GetValue(ResetOnSpaceProperty);
set => SetValue(ResetOnSpaceProperty, value);
}
Type IStyleable.StyleKey => typeof(TextBox);
@ -190,7 +190,7 @@ namespace Avalonia.Controls
{
if (HidePromptOnLeave == true && MaskProvider != null)
{
Text = MaskProvider.ToDisplayString();
SetCurrentValue(TextProperty, MaskProvider.ToDisplayString());
}
base.OnGotFocus(e);
}
@ -225,11 +225,11 @@ namespace Avalonia.Controls
var index = GetNextCharacterPosition(CaretIndex);
if (MaskProvider.InsertAt(item, index))
{
CaretIndex = ++index;
SetCurrentValue(CaretIndexProperty, ++index);
}
}
Text = MaskProvider.ToDisplayString();
SetCurrentValue(TextProperty, MaskProvider.ToDisplayString());
e.Handled = true;
return;
}
@ -279,7 +279,7 @@ namespace Avalonia.Controls
{
if (HidePromptOnLeave && MaskProvider != null)
{
Text = MaskProvider.ToString(!HidePromptOnLeave, true);
SetCurrentValue(TextProperty, MaskProvider.ToString(!HidePromptOnLeave, true));
}
base.OnLostFocus(e);
}
@ -326,15 +326,6 @@ namespace Avalonia.Controls
}
else if (change.Property == PasswordCharProperty)
{
if (!MaskedTextProvider.IsValidPasswordChar(PasswordChar))
{
throw new ArgumentException("Specified character value is not allowed for this property.", nameof(PasswordChar));
}
if (MaskProvider != null && PasswordChar == MaskProvider.PromptChar)
{
// Prompt and password chars must be different.
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
}
if (MaskProvider != null && MaskProvider.PasswordChar != PasswordChar)
{
UpdateMaskProvider();
@ -342,17 +333,23 @@ namespace Avalonia.Controls
}
else if (change.Property == PromptCharProperty)
{
if (!MaskedTextProvider.IsValidInputChar(PromptChar))
if (MaskProvider != null && MaskProvider.PromptChar != PromptChar)
{
throw new ArgumentException("Specified character value is not allowed for this property.");
UpdateMaskProvider();
}
if (PromptChar == PasswordChar)
}
else if (change.Property == ResetOnPromptProperty)
{
if (MaskProvider != null && change.GetNewValue<bool>() is { } newValue)
{
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
MaskProvider.ResetOnPrompt = newValue;
}
if (MaskProvider != null && MaskProvider.PromptChar != PromptChar)
}
else if (change.Property == ResetOnSpaceProperty)
{
if (MaskProvider != null && change.GetNewValue<bool>() is { } newValue)
{
UpdateMaskProvider();
MaskProvider.ResetOnSpace = newValue;
}
}
else if (change.Property == AsciiOnlyProperty && MaskProvider != null && MaskProvider.AsciiOnly != AsciiOnly
@ -390,7 +387,7 @@ namespace Avalonia.Controls
if (CaretIndex < Text?.Length)
{
CaretIndex = GetNextCharacterPosition(CaretIndex);
SetCurrentValue(CaretIndexProperty, GetNextCharacterPosition(CaretIndex));
if (MaskProvider.InsertAt(e.Text!, CaretIndex))
{
@ -399,7 +396,7 @@ namespace Avalonia.Controls
var nextPos = GetNextCharacterPosition(CaretIndex);
if (nextPos != 0 && CaretIndex != Text.Length)
{
CaretIndex = nextPos;
SetCurrentValue(CaretIndexProperty, nextPos);
}
}
@ -434,8 +431,8 @@ namespace Avalonia.Controls
{
if (provider != null)
{
Text = provider.ToDisplayString();
CaretIndex = position;
SetCurrentValue(TextProperty, provider.ToDisplayString());
SetCurrentValue(CaretIndexProperty, position);
}
}

15
src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs

@ -22,7 +22,6 @@ namespace Avalonia.Controls.Presenters
Debug.Assert(presenter.Panel is not null or VirtualizingPanel);
_presenter = presenter;
_presenter.ItemsControl.PropertyChanged += OnItemsControlPropertyChanged;
_presenter.ItemsControl.ItemsView.PostCollectionChanged += OnItemsChanged;
OnItemsChanged(null, CollectionUtils.ResetEventArgs);
@ -32,9 +31,7 @@ namespace Avalonia.Controls.Presenters
{
if (_presenter.ItemsControl is { } itemsControl)
{
itemsControl.PropertyChanged -= OnItemsControlPropertyChanged;
itemsControl.ItemsView.PostCollectionChanged -= OnItemsChanged;
ClearItemsControlLogicalChildren();
}
@ -43,18 +40,6 @@ namespace Avalonia.Controls.Presenters
internal void Refresh() => OnItemsChanged(null, CollectionUtils.ResetEventArgs);
private void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ItemsControl.ItemsProperty)
{
if (e.OldValue is INotifyCollectionChanged inccOld)
inccOld.CollectionChanged -= OnItemsChanged;
OnItemsChanged(null, CollectionUtils.ResetEventArgs);
if (e.NewValue is INotifyCollectionChanged inccNew)
inccNew.CollectionChanged += OnItemsChanged;
}
}
private void OnItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (_presenter.Panel is null || _presenter.ItemsControl is null)

109
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -9,17 +9,13 @@ using Avalonia.VisualTree;
using Avalonia.Layout;
using Avalonia.Media.Immutable;
using Avalonia.Controls.Documents;
using Avalonia.Input.TextInput;
using Avalonia.Data;
namespace Avalonia.Controls.Presenters
{
public class TextPresenter : Control
{
public static readonly DirectProperty<TextPresenter, int> CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner<TextPresenter>(
o => o.CaretIndex,
(o, v) => o.CaretIndex = v);
public static readonly StyledProperty<int> CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner<TextPresenter>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly StyledProperty<bool> RevealPasswordProperty =
AvaloniaProperty.Register<TextPresenter, bool>(nameof(RevealPassword));
@ -36,33 +32,23 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<IBrush?> CaretBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(CaretBrush));
public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
o => o.SelectionStart,
(o, v) => o.SelectionStart = v);
public static readonly StyledProperty<int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly DirectProperty<TextPresenter, int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<TextPresenter>(
o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v);
public static readonly StyledProperty<int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<TextPresenter>(new(coerce: TextBox.CoerceCaretIndex));
/// <summary>
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly DirectProperty<TextPresenter, string?> TextProperty =
AvaloniaProperty.RegisterDirect<TextPresenter, string?>(
nameof(Text),
o => o.Text,
(o, v) => o.Text = v, defaultBindingMode: BindingMode.OneWay);
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<TextPresenter>(new(string.Empty));
/// <summary>
/// Defines the <see cref="PreeditText"/> property.
/// </summary>
public static readonly DirectProperty<TextPresenter, string?> PreeditTextProperty =
AvaloniaProperty.RegisterDirect<TextPresenter, string?>(
nameof(PreeditText),
o => o.PreeditText,
(o, v) => o.PreeditText = v);
public static readonly StyledProperty<string?> PreeditTextProperty =
AvaloniaProperty.Register<TextPresenter, string?>(nameof(PreeditText));
/// <summary>
/// Defines the <see cref="CompositionRegion"/> property.
@ -104,18 +90,13 @@ namespace Avalonia.Controls.Presenters
Border.BackgroundProperty.AddOwner<TextPresenter>();
private readonly DispatcherTimer _caretTimer;
private int _caretIndex;
private int _selectionStart;
private int _selectionEnd;
private bool _caretBlink;
internal string? _text;
private TextLayout? _textLayout;
private Size _constraint;
private CharacterHit _lastCharacterHit;
private Rect _caretBounds;
private Point _navigationPosition;
private string? _preeditText;
private TextRange? _compositionRegion;
static TextPresenter()
@ -125,7 +106,6 @@ namespace Avalonia.Controls.Presenters
public TextPresenter()
{
_text = string.Empty;
_caretTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
_caretTimer.Tick += CaretTimerTick;
}
@ -147,14 +127,14 @@ namespace Avalonia.Controls.Presenters
[Content]
public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public string? PreeditText
{
get => _preeditText;
set => SetAndRaise(PreeditTextProperty, ref _preeditText, value);
get => GetValue(PreeditTextProperty);
set => SetValue(PreeditTextProperty, value);
}
public TextRange? CompositionRegion
@ -275,17 +255,8 @@ namespace Avalonia.Controls.Presenters
public int CaretIndex
{
get
{
return _caretIndex;
}
set
{
if (value != _caretIndex)
{
MoveCaretToTextPosition(value);
}
}
get => GetValue(CaretIndexProperty);
set => SetValue(CaretIndexProperty, value);
}
public char PasswordChar
@ -320,30 +291,14 @@ namespace Avalonia.Controls.Presenters
public int SelectionStart
{
get
{
return _selectionStart;
}
set
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
}
get => GetValue(SelectionStartProperty);
set => SetValue(SelectionStartProperty, value);
}
public int SelectionEnd
{
get
{
return _selectionEnd;
}
set
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
}
get => GetValue(SelectionEndProperty);
set => SetValue(SelectionEndProperty, value);
}
protected override bool BypassFlowDirectionPolicies => true;
@ -535,12 +490,12 @@ namespace Avalonia.Controls.Presenters
{
TextLayout result;
var text = _text;
var text = Text;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight);
var selectionStart = CoerceCaretIndex(SelectionStart);
var selectionEnd = CoerceCaretIndex(SelectionEnd);
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
@ -561,9 +516,9 @@ namespace Avalonia.Controls.Presenters
};
}
else if (!string.IsNullOrEmpty(_preeditText))
else if (!string.IsNullOrEmpty(PreeditText))
{
var preeditHighlight = new ValueSpan<TextRunProperties>(_caretIndex, _preeditText.Length,
var preeditHighlight = new ValueSpan<TextRunProperties>(CaretIndex, PreeditText.Length,
new GenericTextRunProperties(typeface, FontSize,
foregroundBrush: foreground,
textDecorations: TextDecorations.Underline));
@ -643,13 +598,6 @@ namespace Avalonia.Controls.Presenters
return finalSize;
}
private int CoerceCaretIndex(int value)
{
var text = Text;
var length = text?.Length ?? 0;
return Math.Max(0, Math.Min(length, value));
}
private void CaretTimerTick(object? sender, EventArgs e)
{
_caretBlink = !_caretBlink;
@ -865,7 +813,7 @@ namespace Avalonia.Controls.Presenters
if (notify)
{
SetAndRaise(CaretIndexProperty, ref _caretIndex, caretIndex);
SetCurrentValue(CaretIndexProperty, caretIndex);
}
}
@ -887,6 +835,11 @@ namespace Avalonia.Controls.Presenters
{
base.OnPropertyChanged(change);
if (change.Property == CaretIndexProperty)
{
MoveCaretToTextPosition(change.GetNewValue<int>());
}
switch (change.Property.Name)
{
case nameof(PreeditText):

55
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -145,6 +145,11 @@ namespace Avalonia.Controls.Primitives
private BindingHelper? _bindingHelper;
private bool _isSelectionChangeActive;
public SelectingItemsControl()
{
((ItemCollection)ItemsView).SourceChanged += OnItemsViewSourceChanged;
}
/// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
/// </summary>
@ -229,6 +234,7 @@ namespace Avalonia.Controls.Primitives
/// <see cref="SelectedValue"/> property
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? SelectedValueBinding
{
@ -322,7 +328,7 @@ namespace Avalonia.Controls.Primitives
}
else if (_selection != value)
{
if (value.Source != null && value.Source != Items)
if (value.Source != null && value.Source != ItemsView.Source)
{
throw new ArgumentException(
"The supplied ISelectionModel already has an assigned Source but this " +
@ -434,10 +440,9 @@ namespace Avalonia.Controls.Primitives
return null;
}
/// <inheritdoc />
protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
private protected override void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.ItemsCollectionChanged(sender!, e);
base.OnItemsViewCollectionChanged(sender!, e);
if (AlwaysSelected && SelectedIndex == -1 && ItemCount > 0)
{
@ -547,7 +552,7 @@ namespace Avalonia.Controls.Primitives
if (_selection is object)
{
_selection.Source = Items;
_selection.Source = ItemsView.Source;
}
}
@ -635,16 +640,6 @@ namespace Avalonia.Controls.Primitives
{
AutoScrollToSelectedItemIfNecessary();
}
if (change.Property == ItemsProperty && _updateState is null && _selection is object)
{
var newValue = change.GetNewValue<IEnumerable?>();
_selection.Source = newValue;
if (newValue is null)
{
_selection.Clear();
}
}
else if (change.Property == SelectionModeProperty && _selection is object)
{
var newValue = change.GetNewValue<SelectionMode>();
@ -880,6 +875,12 @@ namespace Avalonia.Controls.Primitives
return false;
}
private void OnItemsViewSourceChanged(object? sender, EventArgs e)
{
if (_selection is not null && _updateState is null)
_selection.Source = ItemsView.Source;
}
/// <summary>
/// Called when <see cref="INotifyPropertyChanged.PropertyChanged"/> is raised on
/// <see cref="Selection"/>.
@ -968,7 +969,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="e">The event args.</param>
private void OnSelectionModelLostSelection(object? sender, EventArgs e)
{
if (AlwaysSelected && Items is object)
if (AlwaysSelected && ItemsView.Count > 0)
{
SelectedIndex = 0;
}
@ -998,14 +999,14 @@ namespace Avalonia.Controls.Primitives
}
}
private object FindItemWithValue(object? value)
private object? FindItemWithValue(object? value)
{
if (ItemCount == 0 || value is null)
{
return AvaloniaProperty.UnsetValue;
}
var items = Items;
var items = ItemsView;
var binding = SelectedValueBinding;
if (binding is null)
@ -1169,7 +1170,7 @@ namespace Avalonia.Controls.Primitives
{
if (_updateState is null)
{
model.Source = Items;
model.Source = ItemsView.Source;
}
model.PropertyChanged += OnSelectionModelPropertyChanged;
@ -1231,16 +1232,18 @@ namespace Avalonia.Controls.Primitives
Selection = state.Selection.Value;
}
if (state.SelectedItems.HasValue)
if (_selection is InternalSelectionModel s)
{
SelectedItems = state.SelectedItems.Value;
s.Update(ItemsView.Source, state.SelectedItems);
}
Selection.Source = Items;
if (Items is null)
else
{
Selection.Clear();
if (state.SelectedItems.HasValue)
{
SelectedItems = state.SelectedItems.Value;
}
Selection.Source = ItemsView.Source;
}
if (state.SelectedValue.HasValue)

105
src/Avalonia.Controls/SelectableTextBlock.cs

@ -17,17 +17,11 @@ namespace Avalonia.Controls
/// </summary>
public class SelectableTextBlock : TextBlock, IInlineHost
{
public static readonly DirectProperty<SelectableTextBlock, int> SelectionStartProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, int>(
nameof(SelectionStart),
o => o.SelectionStart,
(o, v) => o.SelectionStart = v);
public static readonly DirectProperty<SelectableTextBlock, int> SelectionEndProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, int>(
nameof(SelectionEnd),
o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v);
public static readonly StyledProperty<int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<SelectableTextBlock>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly StyledProperty<int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<SelectableTextBlock>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly DirectProperty<SelectableTextBlock, string> SelectedTextProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, string>(
@ -35,21 +29,16 @@ namespace Avalonia.Controls
o => o.SelectedText);
public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<SelectableTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue);
TextBox.SelectionBrushProperty.AddOwner<SelectableTextBlock>(new(new Data.Optional<IBrush?>(Brushes.Blue)));
public static readonly DirectProperty<SelectableTextBlock, bool> CanCopyProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, bool>(
nameof(CanCopy),
o => o.CanCopy);
TextBox.CanCopyProperty.AddOwner<SelectableTextBlock>(o => o.CanCopy);
public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent =
RoutedEvent.Register<SelectableTextBlock, RoutedEventArgs>(
nameof(CopyingToClipboard), RoutingStrategies.Bubble);
private bool _canCopy;
private int _selectionStart;
private int _selectionEnd;
private int _wordSelectionStart = -1;
static SelectableTextBlock()
@ -78,16 +67,8 @@ namespace Avalonia.Controls
/// </summary>
public int SelectionStart
{
get => _selectionStart;
set
{
if (SetAndRaise(SelectionStartProperty, ref _selectionStart, value))
{
RaisePropertyChanged(SelectedTextProperty, "", "");
UpdateCommandStates();
}
}
get => GetValue(SelectionStartProperty);
set => SetValue(SelectionStartProperty, value);
}
/// <summary>
@ -95,16 +76,8 @@ namespace Avalonia.Controls
/// </summary>
public int SelectionEnd
{
get => _selectionEnd;
set
{
if (SetAndRaise(SelectionEndProperty, ref _selectionEnd, value))
{
RaisePropertyChanged(SelectedTextProperty, "", "");
UpdateCommandStates();
}
}
get => GetValue(SelectionEndProperty);
set => SetValue(SelectionEndProperty, value);
}
/// <summary>
@ -150,7 +123,7 @@ namespace Avalonia.Controls
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard)))
.SetTextAsync(text);
}
}
}
/// <summary>
/// Select all text in the TextBox
@ -159,8 +132,8 @@ namespace Avalonia.Controls
{
var text = Text;
SelectionStart = 0;
SelectionEnd = text?.Length ?? 0;
SetCurrentValue(SelectionStartProperty, 0);
SetCurrentValue(SelectionEndProperty, text?.Length ?? 0);
}
/// <summary>
@ -168,7 +141,7 @@ namespace Avalonia.Controls
/// </summary>
public void ClearSelection()
{
SelectionEnd = SelectionStart;
SetCurrentValue(SelectionEndProperty, SelectionStart);
}
protected override void OnGotFocus(GotFocusEventArgs e)
@ -240,6 +213,17 @@ namespace Avalonia.Controls
e.Handled = handled;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SelectionStartProperty || change.Property == SelectionEndProperty)
{
RaisePropertyChanged(SelectedTextProperty, "", "");
UpdateCommandStates();
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
@ -271,25 +255,26 @@ namespace Avalonia.Controls
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
SetCurrentValue(SelectionStartProperty, previousWord);
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
SetCurrentValue(SelectionStartProperty, Math.Min(oldIndex, index));
SetCurrentValue(SelectionEndProperty, Math.Max(oldIndex, index));
}
}
else
{
if (_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
SetCurrentValue(SelectionStartProperty, index);
SetCurrentValue(SelectionEndProperty, index);
_wordSelectionStart = -1;
}
@ -299,16 +284,16 @@ namespace Avalonia.Controls
case 2:
if (!StringUtils.IsStartOfWord(text, index))
{
SelectionStart = StringUtils.PreviousWord(text, index);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, index));
}
_wordSelectionStart = SelectionStart;
if (!StringUtils.IsEndOfWord(text, index))
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
break;
case 3:
_wordSelectionStart = -1;
@ -347,22 +332,22 @@ namespace Avalonia.Controls
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, textPosition);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, textPosition));
}
if (distance >= 0)
{
if (SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
SetCurrentValue(SelectionStartProperty, _wordSelectionStart);
}
SelectionEnd = StringUtils.NextWord(text, textPosition);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, textPosition));
}
}
else
{
SelectionEnd = textPosition;
SetCurrentValue(SelectionEndProperty, textPosition);
}
}
@ -395,7 +380,8 @@ namespace Avalonia.Controls
caretIndex >= firstSelection && caretIndex <= lastSelection;
if (!didClickInSelection)
{
SelectionStart = SelectionEnd = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
@ -411,9 +397,8 @@ namespace Avalonia.Controls
private string GetSelection()
{
var text = GetText();
if (string.IsNullOrEmpty(text))
var textLength = Text?.Length ?? 0;
if (textLength == 0)
{
return "";
}
@ -423,14 +408,14 @@ namespace Avalonia.Controls
var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd);
if (start == end || text.Length < end)
if (start == end || textLength < end)
{
return "";
}
var length = Math.Max(0, end - start);
var selectedText = text.Substring(start, length);
var selectedText = Text!.Substring(start, length);
return selectedText;
}

27
src/Avalonia.Controls/Selection/InternalSelectionModel.cs

@ -5,6 +5,7 @@ using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Data;
namespace Avalonia.Controls.Selection
{
@ -13,6 +14,7 @@ namespace Avalonia.Controls.Selection
private IList? _writableSelectedItems;
private int _ignoreModelChanges;
private bool _ignoreSelectedItemsChanges;
private bool _skipSyncFromSelectedItems;
private bool _isResetting;
public InternalSelectionModel()
@ -60,6 +62,29 @@ namespace Avalonia.Controls.Selection
}
}
internal void Update(IEnumerable? source, Optional<IList?> selectedItems)
{
var previousSource = Source;
var previousWritableSelectedItems = _writableSelectedItems;
try
{
_skipSyncFromSelectedItems = true;
SetSource(source);
if (selectedItems.HasValue)
WritableSelectedItems = selectedItems.Value;
}
finally
{
_skipSyncFromSelectedItems = false;
}
// We skipped the sync from WritableSelectedItems before; do it now that both
// the source and WritableSelectedItems are updated.
if (previousSource != Source || previousWritableSelectedItems != _writableSelectedItems)
SyncFromSelectedItems();
}
private protected override void SetSource(IEnumerable? value)
{
if (Source == value)
@ -121,7 +146,7 @@ namespace Avalonia.Controls.Selection
private void SyncFromSelectedItems()
{
if (Source is null || _writableSelectedItems is null)
if (_skipSyncFromSelectedItems || Source is null || _writableSelectedItems is null)
{
return;
}

4
src/Avalonia.Controls/Selection/SelectionModel.cs

@ -30,9 +30,9 @@ namespace Avalonia.Controls.Selection
Source = source;
}
public new IEnumerable<T>? Source
public new IEnumerable? Source
{
get => base.Source as IEnumerable<T>;
get => base.Source;
set => SetSource(value);
}

66
src/Avalonia.Controls/TextBlock.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Layout;
@ -13,6 +14,7 @@ namespace Avalonia.Controls
/// <summary>
/// A control that displays a block of text.
/// </summary>
[DebuggerDisplay("Text = {" + nameof(DebugText) + "}")]
public class TextBlock : Control, IInlineHost
{
/// <summary>
@ -103,11 +105,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly DirectProperty<TextBlock, string?> TextProperty =
AvaloniaProperty.RegisterDirect<TextBlock, string?>(
nameof(Text),
o => o.GetText(),
(o, v) => o.SetText(v));
public static readonly StyledProperty<string?> TextProperty =
AvaloniaProperty.Register<TextBlock, string?>(nameof(Text));
/// <summary>
/// Defines the <see cref="TextAlignment"/> property.
@ -142,14 +141,14 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Inlines"/> property.
/// </summary>
public static readonly StyledProperty<InlineCollection?> InlinesProperty =
AvaloniaProperty.Register<TextBlock, InlineCollection?>(
nameof(Inlines));
public static readonly DirectProperty<TextBlock, InlineCollection?> InlinesProperty =
AvaloniaProperty.RegisterDirect<TextBlock, InlineCollection?>(
nameof(Inlines), t => t.Inlines, (t, v) => t.Inlines = v);
internal string? _text;
protected TextLayout? _textLayout;
protected Size _constraint;
private IReadOnlyList<TextRun>? _textRuns;
private InlineCollection? _inlines;
/// <summary>
/// Initializes static members of the <see cref="TextBlock"/> class.
@ -173,7 +172,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets the <see cref="TextLayout"/> used to render the text.
/// </summary>
public TextLayout TextLayout => _textLayout ??= CreateTextLayout(_text);
public TextLayout TextLayout => _textLayout ??= CreateTextLayout(Text);
/// <summary>
/// Gets or sets the padding to place around the <see cref="Text"/>.
@ -198,10 +197,12 @@ namespace Avalonia.Controls
/// </summary>
public string? Text
{
get => GetText();
set => SetText(value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
private string? DebugText => Text ?? Inlines?.Text;
/// <summary>
/// Gets or sets the font family used to draw the control's text.
/// </summary>
@ -325,8 +326,8 @@ namespace Avalonia.Controls
[Content]
public InlineCollection? Inlines
{
get => GetValue(InlinesProperty);
set => SetValue(InlinesProperty, value);
get => _inlines;
set => SetAndRaise(InlinesProperty, ref _inlines, value);
}
protected override bool BypassFlowDirectionPolicies => true;
@ -590,19 +591,18 @@ namespace Avalonia.Controls
TextLayout.Draw(context, origin);
}
protected virtual string? GetText()
{
return _text ?? Inlines?.Text;
}
protected virtual void SetText(string? text)
private bool _clearTextInternal;
internal void ClearTextInternal()
{
if (HasComplexContent)
_clearTextInternal = true;
try
{
SetCurrentValue(TextProperty, null);
}
finally
{
Inlines?.Clear();
_clearTextInternal = false;
}
SetAndRaise(TextProperty, ref _text, text);
}
/// <summary>
@ -780,6 +780,14 @@ namespace Avalonia.Controls
{
base.OnPropertyChanged(change);
if (change.Property == TextProperty)
{
if (HasComplexContent && !_clearTextInternal)
{
Inlines?.Clear();
}
}
switch (change.Property.Name)
{
case nameof(FontSize):
@ -794,10 +802,10 @@ namespace Avalonia.Controls
case nameof(FlowDirection):
case nameof (Padding):
case nameof (LineHeight):
case nameof (LetterSpacing):
case nameof (MaxLines):
case nameof(Padding):
case nameof(LineHeight):
case nameof(LetterSpacing):
case nameof(MaxLines):
case nameof(Text):
case nameof(TextDecorations):
@ -899,7 +907,7 @@ namespace Avalonia.Controls
continue;
}
if (textRun is TextCharacters)
if (textRun is TextCharacters)
{
var skip = Math.Max(0, textSourceIndex - currentPosition);

433
src/Avalonia.Controls/TextBox.cs

@ -61,11 +61,9 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="CaretIndex"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> CaretIndexProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(CaretIndex),
o => o.CaretIndex,
(o, v) => o.CaretIndex = v);
public static readonly StyledProperty<int> CaretIndexProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(CaretIndex),
coerce: CoerceCaretIndex);
/// <summary>
/// Defines the <see cref="IsReadOnly"/> property
@ -100,42 +98,37 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="SelectionStart"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(SelectionStart),
o => o.SelectionStart,
(o, v) => o.SelectionStart = v);
public static readonly StyledProperty<int> SelectionStartProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(SelectionStart),
coerce: CoerceCaretIndex);
/// <summary>
/// Defines the <see cref="SelectionEnd"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> SelectionEndProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(SelectionEnd),
o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v);
public static readonly StyledProperty<int> SelectionEndProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(SelectionEnd),
coerce: CoerceCaretIndex);
/// <summary>
/// Defines the <see cref="MaxLength"/> property
/// </summary>
public static readonly StyledProperty<int> MaxLengthProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength));
/// <summary>
/// Defines the <see cref="MaxLines"/> property
/// </summary>
public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines));
/// <summary>
/// Defines the <see cref="Text"/> property
/// </summary>
public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text,
(o, v) => o.Text = v,
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<TextBox>(new(
coerce: CoerceText,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
enableDataValidation: true));
/// <summary>
/// Defines the <see cref="TextAlignment"/> property
@ -185,9 +178,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="NewLine"/> property
/// </summary>
public static readonly DirectProperty<TextBox, string> NewLineProperty =
AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine),
textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline);
public static readonly StyledProperty<string> NewLineProperty =
AvaloniaProperty.Register<TextBox, string>(nameof(NewLine), Environment.NewLine);
/// <summary>
/// Defines the <see cref="InnerLeftContent"/> property
@ -242,12 +234,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="UndoLimit"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> UndoLimitProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(UndoLimit),
o => o.UndoLimit,
(o, v) => o.UndoLimit = v,
unsetValue: -1);
public static readonly StyledProperty<int> UndoLimitProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(UndoLimit), UndoRedoHelper<UndoRedoState>.DefaultUndoLimit);
/// <summary>
/// Defines the <see cref="CanUndo"/> property
@ -318,18 +306,13 @@ namespace Avalonia.Controls
public override int GetHashCode() => Text?.GetHashCode() ?? 0;
}
private string? _text;
private int _caretIndex;
private int _selectionStart;
private int _selectionEnd;
private TextPresenter? _presenter;
private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient();
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private readonly TextBoxTextInputMethodClient _imClient = new();
private readonly UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _canCut;
private bool _canCopy;
private bool _canPaste;
private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
private bool _canUndo;
private bool _canRedo;
@ -399,18 +382,19 @@ namespace Avalonia.Controls
/// </summary>
public int CaretIndex
{
get => _caretIndex;
set
{
value = CoerceCaretIndex(value);
SetAndRaise(CaretIndexProperty, ref _caretIndex, value);
get => GetValue(CaretIndexProperty);
set => SetValue(CaretIndexProperty, value);
}
UndoRedoState state;
if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
_undoRedoHelper.UpdateLastState();
private void OnCaretIndexChanged(AvaloniaPropertyChangedEventArgs e)
{
UndoRedoState state;
if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
_undoRedoHelper.UpdateLastState();
SelectionStart = SelectionEnd = value;
}
var newValue = e.GetNewValue<int>();
SetCurrentValue(SelectionStartProperty, newValue);
SetCurrentValue(SelectionEndProperty, newValue);
}
/// <summary>
@ -463,21 +447,18 @@ namespace Avalonia.Controls
/// </summary>
public int SelectionStart
{
get => _selectionStart;
set
{
value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
get => GetValue(SelectionStartProperty);
set => SetValue(SelectionStartProperty, value);
}
if (changed)
{
UpdateCommandStates();
}
private void OnSelectionStartChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateCommandStates();
if (SelectionEnd == value && CaretIndex != value)
{
CaretIndex = value;
}
var value = e.GetNewValue<int>();
if (SelectionEnd == value && CaretIndex != value)
{
SetCurrentValue(CaretIndexProperty, value);
}
}
@ -490,21 +471,18 @@ namespace Avalonia.Controls
/// </remarks>
public int SelectionEnd
{
get => _selectionEnd;
set
{
value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
if (changed)
{
UpdateCommandStates();
}
get => GetValue(SelectionEndProperty);
set => SetValue(SelectionEndProperty, value);
}
private void OnSelectionEndChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateCommandStates();
if (SelectionStart == value && CaretIndex != value)
{
CaretIndex = value;
}
var value = e.GetNewValue<int>();
if (SelectionStart == value && CaretIndex != value)
{
SetCurrentValue(CaretIndexProperty, value);
}
}
@ -550,36 +528,27 @@ namespace Avalonia.Controls
[Content]
public string? Text
{
get => _text;
set
{
var caretIndex = CaretIndex;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value);
// Before #9490, snapshot here was done AFTER text change - this doesn't make sense
// since intial state would never be no text and you'd always have to make a text
// change before undo would be available
// The undo/redo stacks were also cleared at this point, which also doesn't make sense
// as it is still valid to want to undo a programmatic text set
// So we snapshot text now BEFORE the change so we can always revert
// Also don't need to check IsUndoEnabled here, that's done in SnapshotUndoRedo
if (!_isUndoingRedoing)
{
SnapshotUndoRedo();
}
var textChanged = SetAndRaise(TextProperty, ref _text, value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
if (textChanged)
{
RaiseTextChangeEvents();
}
private static string? CoerceText(AvaloniaObject sender, string? value)
{
var textBox = (TextBox)sender;
// Before #9490, snapshot here was done AFTER text change - this doesn't make sense
// since intial state would never be no text and you'd always have to make a text
// change before undo would be available
// The undo/redo stacks were also cleared at this point, which also doesn't make sense
// as it is still valid to want to undo a programmatic text set
// So we snapshot text now BEFORE the change so we can always revert
// Also don't need to check IsUndoEnabled here, that's done in SnapshotUndoRedo
if (!textBox._isUndoingRedoing)
{
textBox.SnapshotUndoRedo();
}
return value;
}
/// <summary>
@ -691,8 +660,8 @@ namespace Avalonia.Controls
/// </summary>
public string NewLine
{
get => _newLine;
set => SetAndRaise(NewLineProperty, ref _newLine, value);
get => GetValue(NewLineProperty);
set => SetValue(NewLineProperty, value);
}
/// <summary>
@ -700,7 +669,8 @@ namespace Avalonia.Controls
/// </summary>
public void ClearSelection()
{
CaretIndex = SelectionStart;
SetCurrentValue(CaretIndexProperty, SelectionStart);
SetCurrentValue(SelectionEndProperty, SelectionStart);
}
/// <summary>
@ -744,25 +714,20 @@ namespace Avalonia.Controls
/// </summary>
public int UndoLimit
{
get => _undoRedoHelper.Limit;
set
{
if (_undoRedoHelper.Limit != value)
{
// can't use SetAndRaise due to using _undoRedoHelper.Limit
// (can't send a ref of a property to SetAndRaise),
// so use RaisePropertyChanged instead.
var oldValue = _undoRedoHelper.Limit;
_undoRedoHelper.Limit = value;
RaisePropertyChanged(UndoLimitProperty, oldValue, value);
}
// from docs at
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
// "Setting UndoLimit clears the undo queue."
_undoRedoHelper.Clear();
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = false;
}
get => GetValue(UndoLimitProperty);
set => SetValue(UndoLimitProperty, value);
}
private void OnUndoLimitChanged(int newValue)
{
_undoRedoHelper.Limit = newValue;
// from docs at
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
// "Setting UndoLimit clears the undo queue."
_undoRedoHelper.Clear();
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = false;
}
/// <summary>
@ -866,9 +831,31 @@ namespace Avalonia.Controls
if (change.Property == TextProperty)
{
CoerceValue(CaretIndexProperty);
CoerceValue(SelectionStartProperty);
CoerceValue(SelectionEndProperty);
RaiseTextChangeEvents();
UpdatePseudoclasses();
UpdateCommandStates();
}
else if (change.Property == CaretIndexProperty)
{
OnCaretIndexChanged(change);
}
else if (change.Property == SelectionStartProperty)
{
OnSelectionStartChanged(change);
}
else if (change.Property == SelectionEndProperty)
{
OnSelectionEndChanged(change);
}
else if (change.Property == UndoLimitProperty)
{
OnUndoLimitChanged(change.GetNewValue<int>());
}
else if (change.Property == IsUndoEnabledProperty && change.GetNewValue<bool>() == false)
{
// from docs at
@ -920,7 +907,7 @@ namespace Avalonia.Controls
(ContextMenu == null || !ContextMenu.IsOpen))
{
ClearSelection();
RevealPassword = false;
SetCurrentValue(RevealPasswordProperty, false);
}
UpdateCommandStates();
@ -986,35 +973,44 @@ namespace Avalonia.Controls
}
}
var text = Text ?? string.Empty;
var newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd);
var currentText = Text ?? string.Empty;
var selectionLength = Math.Abs(SelectionStart - SelectionEnd);
var newLength = input.Length + currentText.Length - selectionLength;
if (MaxLength > 0 && newLength > MaxLength)
{
input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength)));
newLength = MaxLength;
}
if (!string.IsNullOrEmpty(input))
{
var oldText = _text;
DeleteSelection(false);
var textBuilder = StringBuilderCache.Acquire(Math.Max(currentText.Length, newLength));
textBuilder.Append(currentText);
var caretIndex = CaretIndex;
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
ClearSelection();
if (IsUndoEnabled)
if (selectionLength != 0)
{
_undoRedoHelper.DiscardRedo();
var (start, _) = GetSelectionRange();
textBuilder.Remove(start, selectionLength);
caretIndex = start;
}
if (_text != oldText)
textBuilder.Insert(caretIndex, input);
SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(textBuilder));
ClearSelection();
if (IsUndoEnabled)
{
RaisePropertyChanged(TextProperty, oldText, _text);
_undoRedoHelper.DiscardRedo();
}
CaretIndex = caretIndex + input.Length;
SetCurrentValue(CaretIndexProperty, caretIndex + input.Length);
}
}
@ -1168,7 +1164,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheEndOfDocument))
{
@ -1176,7 +1172,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheStartOfLine))
{
@ -1184,7 +1180,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheEndOfLine))
{
@ -1192,31 +1188,31 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveHome(true);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveEnd(true);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveHome(false);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
@ -1224,9 +1220,9 @@ namespace Avalonia.Controls
}
else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveEnd(false);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
@ -1261,11 +1257,11 @@ namespace Avalonia.Controls
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
break;
@ -1283,11 +1279,11 @@ namespace Avalonia.Controls
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
break;
@ -1314,11 +1310,13 @@ namespace Avalonia.Controls
var length = end - start;
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
var sb = StringBuilderCache.Acquire(text.Length);
sb.Append(text);
sb.Remove(start, end - start);
SetTextInternal(editedText);
SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(sb));
CaretIndex = start;
SetCurrentValue(CaretIndexProperty, start);
}
}
@ -1346,9 +1344,11 @@ namespace Avalonia.Controls
var start = Math.Min(nextPosition, caretIndex);
var end = Math.Max(nextPosition, caretIndex);
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
var sb = StringBuilderCache.Acquire(text.Length);
sb.Append(text);
sb.Remove(start, end - start);
SetTextInternal(editedText);
SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(sb));
}
}
@ -1425,7 +1425,7 @@ namespace Avalonia.Controls
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
SetAndRaise(CaretIndexProperty, ref _caretIndex, index);
SetCurrentValue(CaretIndexProperty, index);
switch (e.ClickCount)
{
@ -1438,25 +1438,26 @@ namespace Avalonia.Controls
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
SetCurrentValue(SelectionStartProperty, previousWord);
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
SetCurrentValue(SelectionStartProperty, Math.Min(oldIndex, index));
SetCurrentValue(SelectionEndProperty, Math.Max(oldIndex, index));
}
}
else
{
if(_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
SetCurrentValue(SelectionStartProperty, index);
SetCurrentValue(SelectionEndProperty, index);
_wordSelectionStart = -1;
}
}
@ -1466,14 +1467,14 @@ namespace Avalonia.Controls
if (!StringUtils.IsStartOfWord(text, index))
{
SelectionStart = StringUtils.PreviousWord(text, index);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, index));
}
_wordSelectionStart = SelectionStart;
if (!StringUtils.IsEndOfWord(text, index))
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
break;
@ -1517,22 +1518,22 @@ namespace Avalonia.Controls
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, caretIndex);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, caretIndex));
}
if (distance >= 0)
{
if(SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
SetCurrentValue(SelectionStartProperty, _wordSelectionStart);
}
SelectionEnd = StringUtils.NextWord(text, caretIndex);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, caretIndex));
}
}
else
{
SelectionEnd = caretIndex;
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
}
@ -1565,7 +1566,9 @@ namespace Avalonia.Controls
caretIndex >= firstSelection && caretIndex <= lastSelection;
if (!didClickInSelection)
{
CaretIndex = SelectionEnd = SelectionStart = caretIndex;
SetCurrentValue(CaretIndexProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
SetCurrentValue(SelectionStartProperty, caretIndex);
}
}
@ -1588,10 +1591,10 @@ namespace Avalonia.Controls
}
}
private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text);
private static int CoerceCaretIndex(int value, string? text)
internal static int CoerceCaretIndex(AvaloniaObject sender, int value)
{
var text = sender.GetValue(TextProperty); // method also used by TextPresenter and SelectableTextBlock
if (text == null)
{
return 0;
@ -1619,10 +1622,7 @@ namespace Avalonia.Controls
/// <summary>
/// Clears the text in the TextBox
/// </summary>
public void Clear()
{
Text = string.Empty;
}
public void Clear() => SetCurrentValue(TextProperty, string.Empty);
private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting)
{
@ -1645,7 +1645,7 @@ namespace Avalonia.Controls
LogicalDirection.Forward :
LogicalDirection.Backward);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
@ -1662,7 +1662,7 @@ namespace Avalonia.Controls
LogicalDirection.Backward);
}
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
}
else
@ -1678,17 +1678,17 @@ namespace Avalonia.Controls
offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd;
}
SelectionEnd += offset;
SetCurrentValue(SelectionEndProperty, SelectionEnd + offset);
_presenter.MoveCaretToTextPosition(SelectionEnd);
if (!isSelecting)
{
CaretIndex = SelectionEnd;
SetCurrentValue(CaretIndexProperty, SelectionEnd);
}
else
{
SelectionStart = selectionStart;
SetCurrentValue(SelectionStartProperty, selectionStart);
}
}
}
@ -1747,36 +1747,45 @@ namespace Avalonia.Controls
/// </summary>
public void SelectAll()
{
SelectionStart = 0;
SelectionEnd = Text?.Length ?? 0;
SetCurrentValue(SelectionStartProperty, 0);
SetCurrentValue(SelectionEndProperty, Text?.Length ?? 0);
}
internal bool DeleteSelection(bool raiseTextChanged = true)
private (int start, int end) GetSelectionRange()
{
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
return (Math.Min(selectionStart, selectionEnd), Math.Max(selectionStart, selectionEnd));
}
internal bool DeleteSelection()
{
if (IsReadOnly)
return true;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var (start, end) = GetSelectionRange();
if (selectionStart != selectionEnd)
if (start != end)
{
var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd);
var text = Text!;
var textBuilder = StringBuilderCache.Acquire(text.Length);
SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged);
textBuilder.Append(text);
textBuilder.Remove(start, end - start);
SetCurrentValue(TextProperty, textBuilder.ToString());
_presenter?.MoveCaretToTextPosition(start);
CaretIndex = start;
SetCurrentValue(CaretIndexProperty, start);
ClearSelection();
return true;
}
CaretIndex = SelectionStart;
SetCurrentValue(CaretIndexProperty, SelectionStart);
return false;
}
@ -1826,46 +1835,30 @@ namespace Avalonia.Controls
}, DispatcherPriority.Normal);
}
private void SetTextInternal(string value, bool raiseTextChanged = true)
{
if (raiseTextChanged)
{
bool textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged)
{
RaiseTextChangeEvents();
}
}
else
{
_text = value;
}
}
private void SetSelectionForControlBackspace()
{
var selectionStart = CaretIndex;
MoveHorizontal(-1, true, false);
SelectionStart = selectionStart;
SetCurrentValue(SelectionStartProperty, selectionStart);
}
private void SetSelectionForControlDelete()
{
if (_text == null || _presenter == null)
var textLength = Text?.Length ?? 0;
if (_presenter == null || textLength == 0)
{
return;
}
SelectionStart = CaretIndex;
SetCurrentValue(SelectionStartProperty, CaretIndex);
MoveHorizontal(1, true, true);
if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ')
if (SelectionEnd < textLength && Text![SelectionEnd] == ' ')
{
SelectionEnd++;
SetCurrentValue(SelectionEndProperty, SelectionEnd + 1);
}
}
@ -1881,8 +1874,8 @@ namespace Avalonia.Controls
get => new UndoRedoState(Text, CaretIndex);
set
{
Text = value.Text;
CaretIndex = value.CaretPosition;
SetCurrentValue(TextProperty, value.Text);
SetCurrentValue(CaretIndexProperty, value.CaretPosition);
ClearSelection();
}
}

19
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -106,7 +106,7 @@ namespace Avalonia.Controls
{
if (_presenter != null && _textEditable != null)
{
_presenter.CompositionRegion = new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd);
_presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd));
}
}
@ -177,9 +177,9 @@ namespace Avalonia.Controls
var text = GetText(preeditText);
_presenter._text = text;
_presenter.SetCurrentValue(TextPresenter.TextProperty, text);
_presenter.PreeditText = preeditText;
_presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
_presenter.UpdateCaret(new CharacterHit(_compositionStart + (preeditText != null ? preeditText.Length : 0)), false);
@ -201,9 +201,12 @@ namespace Avalonia.Controls
return preeditText;
}
var text = _presenterText.Substring(0, _compositionStart) + preeditText + _presenterText.Substring(_compositionStart);
var sb = StringBuilderCache.Acquire(_presenterText.Length + preeditText.Length);
return text;
sb.Append(_presenterText);
sb.Insert(_compositionStart, preeditText);
return StringBuilderCache.GetStringAndRelease(sb);
}
public void SetComposingRegion(TextRange? region)
@ -213,7 +216,7 @@ namespace Avalonia.Controls
return;
}
_presenter.CompositionRegion = region;
_presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, region);
}
public void SelectInSurroundingText(int start, int end)
@ -252,9 +255,9 @@ namespace Avalonia.Controls
if (_presenter != null)
{
_presenter.PreeditText = null;
_presenter.ClearValue(TextPresenter.PreeditTextProperty);
_presenter.CompositionRegion = null;
_presenter.ClearValue(TextPresenter.CompositionRegionProperty);
_presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
}

4
src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs

@ -144,13 +144,13 @@ namespace Avalonia.Controls.Utils
{
get
{
return SelectorControl?.Items;
return SelectorControl?.ItemsSource;
}
set
{
if (SelectorControl != null)
{
SelectorControl.Items = value;
SelectorControl.ItemsSource = value;
}
}
}

4
src/Avalonia.Controls/Utils/UndoRedoHelper.cs

@ -4,6 +4,8 @@ namespace Avalonia.Controls.Utils
{
class UndoRedoHelper<TState>
{
public const int DefaultUndoLimit = 10;
private readonly IUndoRedoHost _host;
public interface IUndoRedoHost
@ -23,7 +25,7 @@ namespace Avalonia.Controls.Utils
/// Maximum number of states this helper can store for undo/redo.
/// If -1, no limit is imposed.
/// </summary>
public int Limit { get; set; } = 10;
public int Limit { get; set; } = DefaultUndoLimit;
public bool CanUndo => _currentNode?.Previous != null;

20
src/Avalonia.Controls/VirtualizingPanel.cs

@ -34,7 +34,8 @@ namespace Avalonia.Controls
/// <summary>
/// Gets the items to display.
/// </summary>
protected IReadOnlyList<object?> Items => ItemsControl?.ItemsView ?? ItemsSourceView.Empty;
protected IReadOnlyList<object?> Items => (IReadOnlyList<object?>?)ItemsControl?.ItemsView ??
Array.Empty<object?>();
/// <summary>
/// Gets the <see cref="ItemsControl"/> that the panel is displaying items for.
@ -192,17 +193,13 @@ namespace Avalonia.Controls
throw new InvalidOperationException("The VirtualizingPanel is already attached to an ItemsControl");
ItemsControl = itemsControl;
ItemsControl.PropertyChanged += OnItemsControlPropertyChanged;
ItemsControl.ItemsView.PostCollectionChanged += OnItemsControlItemsChanged;
}
internal void Detach()
{
var itemsControl = EnsureItemsControl();
itemsControl.PropertyChanged -= OnItemsControlPropertyChanged;
itemsControl.ItemsView.PostCollectionChanged -= OnItemsControlItemsChanged;
ItemsControl = null;
Children.Clear();
}
@ -216,20 +213,9 @@ namespace Avalonia.Controls
return ItemsControl;
}
private protected virtual void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ItemsControl.ItemsViewProperty)
{
var (oldValue, newValue) = e.GetOldAndNewValue<ItemsSourceView>();
oldValue.PostCollectionChanged -= OnItemsControlItemsChanged;
Refresh();
newValue.PostCollectionChanged += OnItemsControlItemsChanged;
}
}
private void OnItemsControlItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
OnItemsChanged(_itemsControl?.ItemsView ?? ItemsSourceView.Empty, e);
OnItemsChanged(Items, e);
}
[DoesNotReturn]

29
src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs

@ -7,23 +7,18 @@ namespace Avalonia.Diagnostics.Controls
{
internal class FilterTextBox : TextBox, IStyleable
{
public static readonly DirectProperty<FilterTextBox, bool> UseRegexFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseRegexFilter),
o => o.UseRegexFilter, (o, v) => o.UseRegexFilter = v,
public static readonly StyledProperty<bool> UseRegexFilterProperty =
AvaloniaProperty.Register<FilterTextBox, bool>(nameof(UseRegexFilter),
defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<FilterTextBox, bool> UseCaseSensitiveFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseCaseSensitiveFilter),
o => o.UseCaseSensitiveFilter, (o, v) => o.UseCaseSensitiveFilter = v,
public static readonly StyledProperty<bool> UseCaseSensitiveFilterProperty =
AvaloniaProperty.Register<FilterTextBox, bool>(nameof(UseCaseSensitiveFilter),
defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<FilterTextBox, bool> UseWholeWordFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseWholeWordFilter),
o => o.UseWholeWordFilter, (o, v) => o.UseWholeWordFilter = v,
public static readonly StyledProperty<bool> UseWholeWordFilterProperty =
AvaloniaProperty.Register<FilterTextBox, bool>(nameof(UseWholeWordFilter),
defaultBindingMode: BindingMode.TwoWay);
private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter;
public FilterTextBox()
{
Classes.Add("filter-text-box");
@ -31,20 +26,20 @@ namespace Avalonia.Diagnostics.Controls
public bool UseRegexFilter
{
get => _useRegexFilter;
set => SetAndRaise(UseRegexFilterProperty, ref _useRegexFilter, value);
get => GetValue(UseRegexFilterProperty);
set => SetValue(UseRegexFilterProperty, value);
}
public bool UseCaseSensitiveFilter
{
get => _useCaseSensitiveFilter;
set => SetAndRaise(UseCaseSensitiveFilterProperty, ref _useCaseSensitiveFilter, value);
get => GetValue(UseCaseSensitiveFilterProperty);
set => SetValue(UseCaseSensitiveFilterProperty,value);
}
public bool UseWholeWordFilter
{
get => _useWholeWordFilter;
set => SetAndRaise(UseWholeWordFilterProperty, ref _useWholeWordFilter, value);
get => GetValue(UseWholeWordFilterProperty);
set => SetValue(UseWholeWordFilterProperty, value);
}
Type IStyleable.StyleKey => typeof(TextBox);

2
src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml

@ -40,7 +40,7 @@
BorderBrush="{DynamicResource ThemeControlMidBrush}"
BorderThickness="0,0,0,1"
FontFamily="/Assets/Fonts/SourceSansPro-Regular.ttf"
Items="{Binding History}">
ItemsSource="{Binding History}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">

6
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -124,7 +124,7 @@
</Grid>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled">
<ItemsControl Items="{Binding AppliedStyles}" >
<ItemsControl ItemsSource="{Binding AppliedStyles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="#6C6C6C" Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}">
@ -142,7 +142,7 @@
<TextBlock Grid.Row="0" Text="{Binding Name}" />
</Expander.Header>
<ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}">
<ItemsControl Margin="20,0,0,0" Grid.Row="1" ItemsSource="{Binding Setters}">
<ItemsControl.Styles>
<Style Selector="TextBlock.property-name">
@ -253,7 +253,7 @@
</ScrollViewer>
<Expander Header="Pseudo Classes" Grid.Row="2">
<ItemsControl Items="{Binding PseudoClasses}">
<ItemsControl ItemsSource="{Binding PseudoClasses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />

6
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml

@ -45,7 +45,7 @@
UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}" />
<TreeView Grid.Row="1" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" >
<TreeView Grid.Row="1" ItemsSource="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" >
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:EventTreeNodeBase"
ItemsSource="{Binding Children}">
@ -71,7 +71,7 @@
<Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2">
<ListBox Name="EventsList" Items="{Binding RecordedEvents}"
<ListBox Name="EventsList" ItemsSource="{Binding RecordedEvents}"
SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
<ListBox.ItemTemplate>
@ -108,7 +108,7 @@
<DockPanel Grid.Row="2" LastChildFill="True">
<TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" />
<ListBox Items="{Binding SelectedEvent.EventChain}">
<ListBox ItemsSource="{Binding SelectedEvent.EventChain}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem Classes.handled="{Binding Handled}">

2
src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs

@ -221,7 +221,7 @@ namespace Avalonia.Diagnostics.Views
return CreateControl<ComboBox>(
SelectingItemsControl.SelectedItemProperty, init: c =>
{
c.Items = Enum.GetValues(propertyType);
c.ItemsSource = Enum.GetValues(propertyType);
});
var tb = CreateControl<CommitTextBox>(

2
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

@ -6,7 +6,7 @@
<Grid ColumnDefinitions="0.35*,4,0.65*">
<TreeView Name="tree"
BorderThickness="0"
Items="{Binding Nodes}"
ItemsSource="{Binding Nodes}"
SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TreeNode"

4
src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml

@ -50,7 +50,7 @@
<DataTemplate>
<ItemsControl x:DataType="DataValidationErrors"
Foreground="{DynamicResource SystemControlErrorTextForegroundBrush}"
Items="{Binding}">
ItemsSource="{Binding}">
<ItemsControl.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
@ -98,7 +98,7 @@
</Style>
</Panel.Styles>
<ToolTip.Tip>
<ItemsControl Items="{Binding}" x:DataType="collections:IEnumerable" />
<ItemsControl ItemsSource="{Binding}" x:DataType="collections:IEnumerable" />
</ToolTip.Tip>
<Path Width="14"
Height="14"

6
src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml

@ -136,7 +136,7 @@
x:DataType="internal:ManagedFileChooserViewModel">
<ControlTemplate>
<DockPanel>
<ListBox x:Name="PART_QuickLinks" DockPanel.Dock="Left" Items="{Binding QuickLinks}" SelectedIndex="{Binding QuickLinksSelectedIndex}" Focusable="False">
<ListBox x:Name="PART_QuickLinks" DockPanel.Dock="Left" ItemsSource="{Binding QuickLinks}" SelectedIndex="{Binding QuickLinksSelectedIndex}" Focusable="False">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Spacing="4" Orientation="Horizontal" Background="Transparent">
@ -173,7 +173,7 @@
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,4">
<ComboBox DockPanel.Dock="Right"
IsVisible="{Binding ShowFilters}"
Items="{Binding Filters}"
ItemsSource="{Binding Filters}"
SelectedItem="{Binding SelectedFilter}" />
<TextBox Text="{Binding FileName}" Watermark="File name" IsVisible="{Binding !SelectingFolder}" />
</DockPanel>
@ -220,7 +220,7 @@
<TextBlock Grid.Column="8" Text="Size" />
</Grid>
<ListBox x:Name="PART_Files"
Items="{Binding Items}"
ItemsSource="{Binding Items}"
Margin="0 5"
SelectionMode="{Binding SelectionMode}"
SelectedItems="{Binding SelectedItems}"

4
src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml

@ -7,13 +7,13 @@
<ControlTemplate>
<Menu
IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
ItemsSource="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles>
<Style Selector="MenuItem" x:DataType="NativeMenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="InputGesture" Value="{Binding Gesture}"/>
<Setter Property="Items" Value="{Binding Menu.Items}"/>
<Setter Property="ItemsSource" Value="{Binding Menu.Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
<Setter Property="(NativeMenuBar.EnableMenuItemClickForwarding)" Value="True"/>

2
src/Avalonia.Themes.Simple/Controls/DataValidationErrors.xaml

@ -35,7 +35,7 @@
</Canvas.Styles>
<ToolTip.Tip>
<ItemsControl x:DataType="DataValidationErrors"
Items="{Binding}" />
ItemsSource="{Binding}" />
</ToolTip.Tip>
<Path Data="M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2"
Stroke="{DynamicResource ErrorBrush}"

6
src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml

@ -101,7 +101,7 @@
<ComboBox Margin="0,5,0,0"
DockPanel.Dock="Bottom"
IsVisible="{Binding ShowFilters}"
Items="{Binding Filters}"
ItemsSource="{Binding Filters}"
SelectedItem="{Binding SelectedFilter}" />
<TextBox DockPanel.Dock="Bottom"
@ -114,7 +114,7 @@
BorderBrush="Transparent"
DockPanel.Dock="Left"
Focusable="False"
Items="{Binding QuickLinks}"
ItemsSource="{Binding QuickLinks}"
SelectedIndex="{Binding QuickLinksSelectedIndex}">
<ListBox.ItemTemplate>
<DataTemplate>
@ -160,7 +160,7 @@
</Grid>
<ListBox x:Name="PART_Files"
Margin="0,5"
Items="{Binding Items}"
ItemsSource="{Binding Items}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItems="{Binding SelectedItems}"
SelectionMode="{Binding SelectionMode}">

4
src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml

@ -7,13 +7,13 @@
<Setter Property="Template">
<ControlTemplate>
<Menu IsVisible="{Binding !$parent[TopLevel].(NativeMenu.IsNativeMenuExported)}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
ItemsSource="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles>
<Style Selector="MenuItem" x:DataType="NativeMenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="InputGesture" Value="{Binding Gesture}"/>
<Setter Property="Items" Value="{Binding Menu.Items}"/>
<Setter Property="ItemsSource" Value="{Binding Menu.Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
<Setter Property="(NativeMenuBar.EnableMenuItemClickForwarding)" Value="True"/>

43
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -73,27 +73,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// Infer data type from collection binding on a control that displays items.
var property = context.ParentNodes().OfType<XamlPropertyAssignmentNode>().FirstOrDefault();
var attributeType = context.GetAvaloniaTypes().InheritDataTypeFromItemsAttribute;
var attribute = property?.Property?.GetClrProperty().CustomAttributes
.FirstOrDefault(a => a.Type == attributeType);
if (attribute is not null)
var attributes = property?.Property?.GetClrProperty().CustomAttributes
.Where(a => a.Type == attributeType).ToList();
if (attributes?.Count > 0)
{
var propertyName = (string)attribute.Parameters.First();
XamlAstConstructableObjectNode parentObject;
if (attribute.Properties.TryGetValue("AncestorType", out var type)
&& type is IXamlType xamlType)
{
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>()
.FirstOrDefault(n => n.Type.GetClrType().FullName == xamlType.FullName);
}
else
foreach (var attribute in attributes)
{
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();
}
if (parentObject != null)
{
inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject, propertyName);
var propertyName = (string)attribute.Parameters.First();
XamlAstConstructableObjectNode parentObject;
if (attribute.Properties.TryGetValue("AncestorType", out var type)
&& type is IXamlType xamlType)
{
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>()
.FirstOrDefault(n => n.Type.GetClrType().FullName == xamlType.FullName);
}
else
{
parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();
}
if (parentObject != null)
{
inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject, propertyName);
if (inferredDataContextTypeNode != null)
break;
}
}
}

2
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -150,7 +150,7 @@ namespace Avalonia.Win32
TransparencyLevelHint = WindowTransparencyLevel.Transparent,
Content = new TrayIconMenuFlyoutPresenter()
{
Items = menuItems
ItemsSource = menuItems
}
};

2
src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs

@ -39,7 +39,7 @@ namespace Avalonia.Win32
if (item.Menu != null)
{
newItem.Items = Populate(item.Menu);
newItem.ItemsSource = Populate(item.Menu);
}
else if (item.HasClickHandlers && item is INativeMenuItemExporterEventsImplBridge bridge)
{

55
tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs

@ -72,51 +72,51 @@ namespace Avalonia.Base.UnitTests.Styling
[Fact]
public void Can_Set_Direct_Property_In_Style_Without_Activator()
{
var control = new TextBlock();
var control = new DirectPropertyClass();
var target = new Setter();
var style = new Style(x => x.Is<TextBlock>())
var style = new Style(x => x.Is<DirectPropertyClass>())
{
Setters =
{
new Setter(TextBlock.TextProperty, "foo"),
new Setter(DirectPropertyClass.FooProperty, "foo"),
}
};
Apply(style, control);
Assert.Equal("foo", control.Text);
Assert.Equal("foo", control.Foo);
}
[Fact]
public void Can_Set_Direct_Property_Binding_In_Style_Without_Activator()
{
var control = new TextBlock();
var control = new DirectPropertyClass();
var target = new Setter();
var source = new BehaviorSubject<object?>("foo");
var style = new Style(x => x.Is<TextBlock>())
var style = new Style(x => x.Is<DirectPropertyClass>())
{
Setters =
{
new Setter(TextBlock.TextProperty, source.ToBinding()),
new Setter(DirectPropertyClass.FooProperty, source.ToBinding()),
}
};
Apply(style, control);
Assert.Equal("foo", control.Text);
Assert.Equal("foo", control.Foo);
}
[Fact]
public void Cannot_Set_Direct_Property_Binding_In_Style_With_Activator()
{
var control = new TextBlock();
var control = new DirectPropertyClass();
var target = new Setter();
var source = new BehaviorSubject<object?>("foo");
var style = new Style(x => x.Is<TextBlock>().Class("foo"))
var style = new Style(x => x.Is<DirectPropertyClass>().Class("foo"))
{
Setters =
{
new Setter(TextBlock.TextProperty, source.ToBinding()),
new Setter(DirectPropertyClass.FooProperty, source.ToBinding()),
}
};
@ -126,13 +126,13 @@ namespace Avalonia.Base.UnitTests.Styling
[Fact]
public void Cannot_Set_Direct_Property_In_Style_With_Activator()
{
var control = new TextBlock();
var control = new DirectPropertyClass();
var target = new Setter();
var style = new Style(x => x.Is<TextBlock>().Class("foo"))
var style = new Style(x => x.Is<DirectPropertyClass>().Class("foo"))
{
Setters =
{
new Setter(TextBlock.TextProperty, "foo"),
new Setter(DirectPropertyClass.FooProperty, "foo"),
}
};
@ -288,18 +288,18 @@ namespace Avalonia.Base.UnitTests.Styling
{
using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface);
var data = new Data { Foo = "foo" };
var control = new TextBox
var control = new DirectPropertyClass
{
DataContext = data,
};
var style = new Style(x => x.OfType<TextBox>())
var style = new Style(x => x.OfType<DirectPropertyClass>())
{
Setters =
{
new Setter
{
Property = TextBox.TextProperty,
Property = DirectPropertyClass.FooProperty,
Value = new Binding
{
Path = "Foo",
@ -310,9 +310,9 @@ namespace Avalonia.Base.UnitTests.Styling
};
Apply(style, control);
Assert.Equal("foo", control.Text);
Assert.Equal("foo", control.Foo);
control.Text = "bar";
control.Foo = "bar";
Assert.Equal("bar", data.Foo);
}
@ -502,9 +502,9 @@ namespace Avalonia.Base.UnitTests.Styling
Assert.Equal(Brushes.Blue, data.Bar);
}
private void Apply(Style style, Control control)
private void Apply(Style style, StyledElement element)
{
StyleHelpers.TryAttach(style, control);
StyleHelpers.TryAttach(style, element);
}
private void Apply(Setter setter, Control control)
@ -535,5 +535,18 @@ namespace Avalonia.Base.UnitTests.Styling
throw new NotImplementedException();
}
}
private class DirectPropertyClass : StyledElement
{
public static readonly DirectProperty<DirectPropertyClass, string?> FooProperty = AvaloniaProperty.RegisterDirect<DirectPropertyClass, string?>(nameof(Foo),
x => x.Foo, (x, v) => x.Foo = v);
private string? _foo;
public string? Foo
{
get => _foo;
set => SetAndRaise(FooProperty, ref _foo, value);
}
}
}
}

20
tests/Avalonia.Controls.UnitTests/CarouselTests.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = new[]
ItemsSource = new[]
{
"Foo",
"Bar"
@ -43,7 +43,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = new[]
ItemsSource = new[]
{
"Foo",
"Bar"
@ -73,7 +73,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = items,
ItemsSource = items,
};
Prepare(target);
@ -88,7 +88,7 @@ namespace Avalonia.Controls.UnitTests
newItems.RemoveAt(0);
Layout(target);
target.Items = newItems;
target.ItemsSource = newItems;
Layout(target);
child = GetContainerTextBlock(target.GetRealizedContainers().Single());
@ -104,7 +104,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = items,
ItemsSource = items,
};
Prepare(target);
@ -133,7 +133,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = items,
ItemsSource = items,
};
Prepare(target);
@ -144,7 +144,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal("Foo", child.Text);
target.Items = null;
target.ItemsSource = null;
Layout(target);
var numChildren = target.GetRealizedContainers().Count();
@ -166,7 +166,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = items,
ItemsSource = items,
SelectedIndex = 2
};
@ -193,7 +193,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = items,
ItemsSource = items,
};
Prepare(target);
@ -225,7 +225,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Carousel
{
Template = CarouselTemplate(),
Items = items,
ItemsSource = items,
};
Prepare(target);

88
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new ComboBox
{
Items = new[] { "Foo", "Bar" },
ItemsSource = new[] { "Foo", "Bar" },
};
_helper.Down(target);
@ -43,7 +43,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new ComboBox
{
Items = new[] { "Foo", "Bar" },
ItemsSource = new[] { "Foo", "Bar" },
};
_helper.Down(target);
@ -66,15 +66,14 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var items = new[]
{
new ComboBoxItem() { Content = "bla" },
new ComboBoxItem() { Content = "dd" },
new ComboBoxItem() { Content = "sdf", IsEnabled = false }
};
var target = new ComboBox
{
Items = items,
Items =
{
new ComboBoxItem() { Content = "bla" },
new ComboBoxItem() { Content = "dd" },
new ComboBoxItem() { Content = "sdf", IsEnabled = false }
},
Template = GetTemplate(),
WrapSelection = true
};
@ -104,15 +103,14 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var items = new[]
{
new ComboBoxItem() { Content = "bla" },
new ComboBoxItem() { Content = "dd", IsEnabled = false },
new ComboBoxItem() { Content = "sdf" }
};
var target = new ComboBox
{
Items = items,
Items =
{
new ComboBoxItem() { Content = "bla" },
new ComboBoxItem() { Content = "dd", IsEnabled = false },
new ComboBoxItem() { Content = "sdf" }
},
Template = GetTemplate()
};
var root = new TestRoot(target);
@ -139,10 +137,9 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void SelectionBoxItem_Is_Rectangle_With_VisualBrush_When_Selection_Is_Control()
{
var items = new[] { new Canvas() };
var target = new ComboBox
{
Items = items,
Items = { new Canvas() },
SelectedIndex = 0,
};
var root = new TestRoot(target);
@ -152,7 +149,7 @@ namespace Avalonia.Controls.UnitTests
var brush = rectangle.Fill as VisualBrush;
Assert.NotNull(brush);
Assert.Same(items[0], brush.Visual);
Assert.Same(target.Items[0], brush.Visual);
}
[Fact]
@ -160,7 +157,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new ComboBox
{
Items = new[] { new Canvas() },
Items = { new Canvas() },
SelectedIndex = 0,
Template = GetTemplate(),
};
@ -218,7 +215,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new ComboBox
{
Items = new[] { new Canvas() },
Items = { new Canvas() },
SelectedIndex = 0,
Template = GetTemplate(),
};
@ -257,7 +254,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ComboBox
{
Template = GetTemplate(),
Items = items.Select(x => new ComboBoxItem { Content = x })
ItemsSource = items.Select(x => new ComboBoxItem { Content = x })
};
target.ApplyTemplate();
@ -326,7 +323,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ComboBox
{
Items = new[] { new Canvas() },
Items = { new Canvas() },
SelectedIndex = 0,
Template = GetTemplate(),
};
@ -363,17 +360,16 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight()
{
var items = new[]
{
new ComboBoxItem()
{
Content = new Control()
}
};
var target = new ComboBox
{
FlowDirection = FlowDirection.RightToLeft,
Items = items,
Items =
{
new ComboBoxItem()
{
Content = new Control()
}
},
Template = GetTemplate()
};
@ -393,16 +389,15 @@ namespace Avalonia.Controls.UnitTests
{
Child = new Control()
};
var items = new[]
{
new ComboBoxItem()
{
Content = parentContent.Child
}
};
var target = new ComboBox
{
Items = items,
Items =
{
new ComboBoxItem()
{
Content = parentContent.Child
}
},
Template = GetTemplate()
};
@ -428,17 +423,16 @@ namespace Avalonia.Controls.UnitTests
{
Child = new Control()
};
var items = new[]
{
new ComboBoxItem()
{
Content = parentContent.Child
}
};
var target = new ComboBox
{
FlowDirection = FlowDirection.RightToLeft,
Items = items,
Items =
{
new ComboBoxItem()
{
Content = parentContent.Child
}
},
Template = GetTemplate()
};

225
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -16,6 +16,46 @@ namespace Avalonia.Controls.UnitTests
{
public class ItemsControlTests
{
[Fact]
public void Setting_ItemsSource_Should_Populate_Items()
{
var target = new ItemsControl
{
Template = GetTemplate(),
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
ItemsSource = new[] { "foo", "bar" },
};
Assert.NotSame(target.ItemsSource, target.Items);
Assert.Equal(target.ItemsSource, target.Items);
}
[Fact]
public void Cannot_Set_ItemsSource_With_Items_Present()
{
var target = new ItemsControl
{
Template = GetTemplate(),
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
Items = { "foo", "bar" },
};
Assert.Throws<InvalidOperationException>(() => target.ItemsSource = new[] { "baz" });
}
[Fact]
public void Cannot_Modify_Items_When_ItemsSource_Set()
{
var target = new ItemsControl
{
Template = GetTemplate(),
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
ItemsSource = Array.Empty<string>(),
};
Assert.Throws<InvalidOperationException>(() => target.Items.Add("foo"));
}
[Fact]
public void Should_Use_ItemTemplate_To_Create_Control()
{
@ -25,7 +65,7 @@ namespace Avalonia.Controls.UnitTests
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
@ -44,7 +84,7 @@ namespace Avalonia.Controls.UnitTests
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
@ -66,7 +106,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl();
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
@ -79,7 +119,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl();
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter!.ApplyTemplate();
@ -92,7 +132,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl();
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
@ -111,7 +151,7 @@ namespace Avalonia.Controls.UnitTests
};
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
@ -134,7 +174,7 @@ namespace Avalonia.Controls.UnitTests
target.TemplatedParent = templatedParent;
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
root.ApplyTemplate();
target.ApplyTemplate();
@ -153,7 +193,7 @@ namespace Avalonia.Controls.UnitTests
var child = new Control();
target.Template = GetTemplate();
target.Items = new[] { child };
target.Items.Add(child);
Assert.Equal(child.Parent, target);
Assert.Equal(child.GetLogicalParent(), target);
@ -171,7 +211,7 @@ namespace Avalonia.Controls.UnitTests
var child = new Control();
target.Template = GetTemplate();
target.Items = new[] { child };
target.Items.Add(child);
root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(target, child.Parent);
@ -188,7 +228,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
var root = new TestRoot(true, target);
@ -206,11 +246,13 @@ namespace Avalonia.Controls.UnitTests
{
var target = new ItemsControl();
var child = new Control();
var items = new AvaloniaList<Control>(child);
target.Template = GetTemplate();
target.Items = items;
items.RemoveAt(0);
target.Items.Add(child);
Assert.Single(target.GetLogicalChildren());
target.Items.RemoveAt(0);
Assert.Null(child.Parent);
Assert.Null(child.GetLogicalParent());
@ -224,151 +266,127 @@ namespace Avalonia.Controls.UnitTests
var child = new Control();
target.Template = GetTemplate();
target.Items = new[] { child };
target.Items = null;
target.Items.Add(child);
Assert.Null(child.Parent);
Assert.Null(((ILogical)child).LogicalParent);
}
[Fact]
public void Clearing_Items_Should_Clear_Child_Controls_Parent()
{
var target = new ItemsControl();
var child = new Control();
Assert.Single(target.GetLogicalChildren());
target.Template = GetTemplate();
target.Items = new[] { child };
target.ApplyTemplate();
target.Items = null;
target.Items.Clear();
Assert.Null(child.Parent);
Assert.Null(((ILogical)child).LogicalParent);
}
[Fact]
public void Adding_Control_Item_Should_Make_Control_Appear_In_LogicalChildren()
public void Assigning_ItemsSource_Should_Not_Fire_LogicalChildren_CollectionChanged_Before_ApplyTemplate()
{
var target = new ItemsControl();
var child = new Control();
var called = false;
target.Template = GetTemplate();
target.Items = new[] { child };
// Should appear both before and after applying template.
Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren());
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
target.ApplyTemplate();
var list = new AvaloniaList<Control>(new[] { child });
target.ItemsSource = list;
Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren());
Assert.False(called);
}
[Fact]
public void Adding_String_Item_Should_Make_ContentPresenter_Appear_In_LogicalChildren()
public void Changing_ItemsSource_Should_Not_Fire_LogicalChildren_CollectionChanged_Before_ApplyTemplate()
{
var target = new ItemsControl();
var child = new Control();
var called = false;
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
var logical = (ILogical)target;
Assert.Equal(1, logical.LogicalChildren.Count);
Assert.IsType<ContentPresenter>(logical.LogicalChildren[0]);
var list = new AvaloniaList<Control>();
target.ItemsSource = list;
list.Add(child);
Assert.False(called);
}
[Fact]
public void Setting_Items_To_Null_Should_Remove_LogicalChildren()
public void Clearing_Items_Should_Clear_Child_Controls_Parent()
{
var target = new ItemsControl();
var child = new Control();
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.Items.Add(child);
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.Items.Clear();
Assert.NotEmpty(target.GetLogicalChildren());
target.Items = null;
Assert.Equal(new ILogical[0], target.GetLogicalChildren());
Assert.Null(child.Parent);
Assert.Null(((ILogical)child).LogicalParent);
}
[Fact]
public void Setting_Items_Should_Fire_LogicalChildren_CollectionChanged()
public void Adding_Control_Item_Should_Make_Control_Appear_In_LogicalChildren()
{
var target = new ItemsControl();
var child = new Control();
var called = false;
target.Template = GetTemplate();
target.ApplyTemplate();
target.Items.Add(child);
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Add;
// Should appear both before and after applying template.
Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren());
target.Items = new[] { child };
target.ApplyTemplate();
Assert.True(called);
Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren());
}
[Fact]
public void Setting_Items_To_Null_Should_Fire_LogicalChildren_CollectionChanged()
public void Adding_String_Item_Should_Make_ContentPresenter_Appear_In_LogicalChildren()
{
var target = new ItemsControl();
var child = new Control();
var called = false;
target.Template = GetTemplate();
target.Items = new[] { child };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Remove;
target.Items = null;
Assert.True(called);
var logical = (ILogical)target;
Assert.Equal(1, logical.LogicalChildren.Count);
Assert.IsType<ContentPresenter>(logical.LogicalChildren[0]);
}
[Fact]
public void Changing_Items_Should_Fire_LogicalChildren_CollectionChanged()
public void Adding_Items_Should_Fire_LogicalChildren_CollectionChanged()
{
var target = new ItemsControl();
var child = new Control();
var called = false;
target.Template = GetTemplate();
target.Items = new[] { child };
target.ApplyTemplate();
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Add;
target.Items = new[] { "Foo" };
target.Items.Add(child);
Assert.True(called);
}
[Fact]
public void Adding_Items_Should_Fire_LogicalChildren_CollectionChanged()
public void Clearing_Items_Should_Fire_LogicalChildren_CollectionChanged()
{
var target = new ItemsControl();
var items = new AvaloniaList<string> { "Foo" };
var child = new Control();
var called = false;
target.Template = GetTemplate();
target.Items = items;
target.Items.Add(child);
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Add;
called = e.Action == NotifyCollectionChangedAction.Remove;
items.Add("Bar");
target.Items.Clear();
Assert.True(called);
}
@ -381,16 +399,13 @@ namespace Avalonia.Controls.UnitTests
var called = false;
target.Template = GetTemplate();
target.Items = items;
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.ItemsSource = items;
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Remove;
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
items.Remove("Bar");
Assert.True(called);
Assert.False(called);
}
[Fact]
@ -418,7 +433,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new ItemsControl
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = GetTemplate(),
};
@ -451,7 +466,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = new[] { 1, 2, 3 },
ItemsSource = new[] { 1, 2, 3 },
};
Assert.DoesNotContain(":empty", target.Classes);
@ -474,10 +489,10 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = new[] { 1, 2, 3 },
ItemsSource = new[] { 1, 2, 3 },
};
target.Items = new int[0];
target.ItemsSource = new int[0];
Assert.Contains(":empty", target.Classes);
}
@ -488,7 +503,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = new[] { 1, 2, 3 },
ItemsSource = new[] { 1, 2, 3 },
};
Assert.Equal(3, target.ItemCount);
@ -502,7 +517,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
items.Add(4);
@ -522,7 +537,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
items.Clear();
@ -538,7 +553,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
items.Add(1);
@ -547,14 +562,14 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void Single_Item_Class_Should_Be_Set_When_Items_Collection_Count_Increases_To_One()
public void Single_Item_Class_Should_Be_Set_When_ItemsSource_Collection_Count_Increases_To_One()
{
var items = new ObservableCollection<int>() { };
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
items.Add(1);
@ -570,7 +585,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
items.Clear();
@ -586,7 +601,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl()
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
items.Add(2);
@ -613,7 +628,7 @@ namespace Avalonia.Controls.UnitTests
{
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
},
Items = items,
ItemsSource = items,
};
target.ApplyTemplate();
@ -641,7 +656,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
target.ApplyTemplate();
@ -665,7 +680,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
var root = new TestRoot { Child = target };
@ -701,7 +716,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl
{
Template = GetTemplate(),
Items = items,
ItemsSource = items,
};
var root = new TestRoot { Child = target };
@ -729,7 +744,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl
{
Template = GetTemplate(),
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
@ -755,7 +770,7 @@ namespace Avalonia.Controls.UnitTests
DisplayMemberBinding = new Binding("Length")
};
target.Items = new[] { "Foo" };
target.ItemsSource = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
@ -774,7 +789,7 @@ namespace Avalonia.Controls.UnitTests
DisplayMemberBinding = new Binding("Value")
};
target.Items = new[] { new Item("Foo", "Bar") };
target.ItemsSource = new[] { new Item("Foo", "Bar") };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();

39
tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs

@ -38,6 +38,35 @@ namespace Avalonia.Controls.UnitTests
Assert.Throws<ArgumentException>(() => ItemsSourceView.GetOrCreate(source));
}
[Fact]
public void Reassigning_Source_Unsubscribes_From_Previous_Source()
{
var source = new AvaloniaList<string>();
var target = new ReassignableItemsSourceView(source);
var debug = (INotifyCollectionChangedDebug)source;
target.CollectionChanged += (s, e) => { };
Assert.Equal(1, debug.GetCollectionChangedSubscribers().Length);
target.SetSource(new string[0]);
Assert.Null(debug.GetCollectionChangedSubscribers());
}
[Fact]
public void Reassigning_Source_Subscribes_To_New_Source()
{
var source = new AvaloniaList<string>();
var target = new ReassignableItemsSourceView(new string[0]);
var debug = (INotifyCollectionChangedDebug)source;
target.CollectionChanged += (s, e) => { };
target.SetSource(source);
Assert.Equal(1, debug.GetCollectionChangedSubscribers().Length);
}
private class InvalidCollection : INotifyCollectionChanged, IEnumerable<string>
{
public event NotifyCollectionChangedEventHandler CollectionChanged { add { } remove { } }
@ -52,5 +81,15 @@ namespace Avalonia.Controls.UnitTests
yield break;
}
}
private class ReassignableItemsSourceView : ItemsSourceView
{
public ReassignableItemsSourceView(IEnumerable source)
: base(source)
{
}
public new void SetSource(IEnumerable source) => base.SetSource(source);
}
}
}

43
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = new[] { "Foo" },
ItemsSource = new[] { "Foo" },
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
@ -81,7 +81,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
};
Prepare(target);
@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
ItemContainerTheme = theme,
};
@ -124,12 +124,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var items = new[] { "Foo", "Bar", "Baz " };
var theme = new ControlTheme(typeof(ListBoxItem));
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = new[] { new ListBoxItem() },
Items = { new ListBoxItem() },
ItemContainerTheme = theme,
};
@ -149,7 +148,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
Prepare(target);
@ -184,7 +183,7 @@ namespace Avalonia.Controls.UnitTests
{
new FuncDataTemplate<Item>((x, _) => new Button { Content = x })
},
Items = items,
ItemsSource = items,
};
Prepare(target);
@ -208,7 +207,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemsSource = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
SelectedIndex = 0,
};
@ -236,7 +235,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemsSource = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectedIndex = 0,
};
@ -258,7 +257,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectedIndex = 0,
};
@ -272,7 +271,7 @@ namespace Avalonia.Controls.UnitTests
items.Remove("Item 2");
Layout(target);
var actual = target.Presenter.Panel.Children.Cast<ListBoxItem>().Select(x => (string)x.Content).ToList();
var actual = target.GetRealizedContainers().Cast<ListBoxItem>().Select(x => (string)x.Content).ToList();
Assert.Equal(items.OrderBy(x => x), actual.OrderBy(x => x));
}
}
@ -286,7 +285,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.Toggle,
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
};
@ -318,7 +317,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
};
@ -364,7 +363,7 @@ namespace Avalonia.Controls.UnitTests
lm.ExecuteInitialLayoutPass();
target.Items = items;
target.ItemsSource = items;
lm.ExecuteLayoutPass();
@ -415,7 +414,7 @@ namespace Avalonia.Controls.UnitTests
Height = 100,
Width = 50,
ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
Items = items,
ItemsSource = items,
};
wnd.Content = target;
@ -450,7 +449,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
SelectionMode = SelectionMode.AlwaysSelected,
};
@ -497,7 +496,7 @@ namespace Avalonia.Controls.UnitTests
AutoScrollToSelectedItem = true,
Width = 50,
ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
Items = items,
ItemsSource = items,
};
wnd.Content = target;
@ -533,7 +532,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new ListBox
{
[!ListBox.ItemsProperty] = new Binding("Items"),
[!ListBox.ItemsSourceProperty] = new Binding("Items"),
[!ListBox.SelectedItemsProperty] = new Binding("SelectedItems"),
};
@ -650,7 +649,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = new[] { "Foo" },
ItemsSource = new[] { "Foo" },
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
SelectionMode = SelectionMode.AlwaysSelected,
};
@ -672,7 +671,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas { Height = 10 }),
};
@ -703,7 +702,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas { Height = 10 }),
AutoScrollToSelectedItem = true,
SelectedIndex = 1,
@ -749,7 +748,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
WrapSelection = true
};

6
tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Multiple
};
@ -41,7 +41,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Multiple
};
@ -64,7 +64,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Multiple
};

24
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -26,7 +26,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
ApplyTemplate(target);
@ -45,7 +45,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
ApplyTemplate(target);
@ -64,7 +64,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
ApplyTemplate(target);
@ -86,7 +86,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
@ -116,7 +116,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
@ -134,7 +134,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
@ -154,7 +154,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Toggle,
};
@ -196,7 +196,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
@ -217,7 +217,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
@ -236,7 +236,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
ApplyTemplate(target);
@ -259,7 +259,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
DataContext = viewModel,
Items = viewModel.Items
ItemsSource = viewModel.Items
};
target.Bind(ListBox.SelectedItemProperty,

10
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests
return true;
});
var target = new MenuItem();
var contextMenu = new ContextMenu { Items = new AvaloniaList<MenuItem> { target } };
var contextMenu = new ContextMenu { Items = { target } };
var window = new Window { Content = new Panel { ContextMenu = contextMenu } };
window.ApplyStyling();
window.ApplyTemplate();
@ -270,8 +270,8 @@ namespace Avalonia.Controls.UnitTests
return true;
});
var target = new MenuItem();
var parentMenuItem = new MenuItem { Items = new AvaloniaList<MenuItem> { target } };
var contextMenu = new ContextMenu { Items = new AvaloniaList<MenuItem> { parentMenuItem } };
var parentMenuItem = new MenuItem { Items = { target } };
var contextMenu = new ContextMenu { Items = { parentMenuItem } };
var window = new Window { Content = new Panel { ContextMenu = contextMenu } };
window.ApplyStyling();
window.ApplyTemplate();
@ -317,12 +317,12 @@ namespace Avalonia.Controls.UnitTests
MenuItem childMenu2;
var menu = new Menu
{
Items = new[]
Items =
{
(topLevelMenu = new MenuItem
{
Header = "Foo",
Items = new[]
Items =
{
(childMenu1 = new MenuItem { Header = "Bar" }),
(childMenu2 = new MenuItem { Header = "Baz" }),

4
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@ -114,7 +114,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var panel = Assert.IsType<StackPanel>(target.Panel);
var newItems = new[] { "qux", "quux", "corge" };
itemsControl.Items = newItems;
itemsControl.ItemsSource = newItems;
root.LayoutManager.ExecuteLayoutPass();
AssertContainers(panel, newItems);
@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var itemsControl = new ItemsControl
{
Items = items,
ItemsSource = items,
Template = new FuncControlTemplate<ItemsControl>((_, _) => result)
};

4
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -395,7 +395,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var item = new Border();
var root = PreparedWindow(target = new PopupItemsControl
{
Items = new[] { item },
Items = { item },
Template = new FuncControlTemplate<PopupItemsControl>(PopupItemsControlTemplate),
}); ;
root.Show();
@ -490,7 +490,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var item = new Border();
var root = PreparedWindow(target = new PopupItemsControl
{
Items = new[] { item },
Items = { item },
Template = new FuncControlTemplate<PopupItemsControl>(PopupItemsControlTemplate),
});
root.Show();

166
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -43,7 +43,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -82,7 +82,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -105,7 +105,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -127,7 +127,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox();
target.BeginInit();
target.Items = items;
target.ItemsSource = items;
target.Template = Template();
target.EndInit();
@ -144,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
var target = new ListBox();
target.Items = items;
target.ItemsSource = items;
target.Template = Template();
target.DataContext = new object();
@ -163,7 +163,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox();
target.BeginInit();
target.SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected;
target.Items = items;
target.ItemsSource = items;
target.Template = Template();
target.EndInit();
@ -184,7 +184,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
listBox.SelectedIndex = 1;
var items = new AvaloniaList<string>();
listBox.Items = items;
listBox.ItemsSource = items;
items.Add("A");
items.Add("B");
items.Add("C");
@ -202,7 +202,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single,
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
SelectedIndex = 1
};
@ -220,7 +220,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single,
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
SelectedIndex = 2
};
@ -240,7 +240,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var listBox = new ListBox
{
SelectionMode = SelectionMode.Single,
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
SelectedItem = "bar"
};
@ -259,7 +259,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var listBox = new ListBox
{
SelectionMode = SelectionMode.Multiple,
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
};
var selected = new[] { "foo", "bar" };
@ -282,7 +282,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var listBox = new ListBox
{
SelectionMode = SelectionMode.Multiple,
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
};
var selected = new[] { "foo", "bar" };
@ -308,7 +308,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected,
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
SelectedIndex = 1
};
@ -331,7 +331,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -353,7 +353,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -376,7 +376,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -403,7 +403,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -430,7 +430,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -451,7 +451,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -487,7 +487,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -509,7 +509,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -519,7 +519,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(items[1], target.SelectedItem);
Assert.Equal(1, target.SelectedIndex);
target.Items = null;
target.ItemsSource = null;
Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
@ -536,7 +536,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -575,7 +575,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectionMode = SelectionMode.AlwaysSelected,
};
@ -611,7 +611,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl();
target.BeginInit();
target.Items = items;
target.ItemsSource = items;
target.Template = Template();
target.EndInit();
@ -648,7 +648,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -684,7 +684,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -722,7 +722,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -749,7 +749,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -779,7 +779,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -807,7 +807,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -839,7 +839,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -868,7 +868,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedIndex = 1,
};
@ -896,7 +896,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -932,7 +932,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -968,7 +968,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectionMode = SelectionMode.AlwaysSelected,
};
@ -1004,7 +1004,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectionMode = SelectionMode.AlwaysSelected,
};
@ -1041,7 +1041,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -1078,7 +1078,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
((ISupportInitialize)target).BeginInit();
target.SelectedIndex = 1;
target.Items = items;
target.ItemsSource = items;
((ISupportInitialize)target).EndInit();
Prepare(target);
@ -1096,7 +1096,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
((ISupportInitialize)target).BeginInit();
target.SelectedItem = "Bar";
target.Items = items;
target.ItemsSource = items;
((ISupportInitialize)target).EndInit();
Prepare(target);
@ -1127,7 +1127,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var itemsBinding = new Binding("Child.Items");
var selectedBinding = new Binding("Child.SelectedItem");
target.Bind(SelectingItemsControl.ItemsProperty, itemsBinding);
target.Bind(SelectingItemsControl.ItemsSourceProperty, itemsBinding);
target.Bind(SelectingItemsControl.SelectedItemProperty, selectedBinding);
Assert.Equal(1, target.SelectedIndex);
@ -1163,13 +1163,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
var root = new SelectingItemsControl
{
Template = Template(),
Items = new Control[]
ItemsSource = new Control[]
{
new Border(),
nested = new ListBox
{
Template = Template(),
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
SelectedIndex = 1,
}
},
@ -1197,7 +1197,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
@ -1221,7 +1221,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
@ -1245,7 +1245,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
itemsMock.Object.AddRange(new[] { "Foo", "Bar", "Baz" });
var target = new SelectingItemsControl
{
Items = itemsMock.Object
ItemsSource = itemsMock.Object
};
target.SelectedIndex = 1;
@ -1268,7 +1268,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.BeginInit();
root.Child = target;
DelayedBinding.Add(target, ItemsControl.ItemsProperty, new Binding(nameof(RootWithItems.Items)));
DelayedBinding.Add(target, ItemsControl.ItemsSourceProperty, new Binding(nameof(RootWithItems.Items)));
DelayedBinding.Add(target, ListBox.SelectedItemProperty, new Binding(nameof(RootWithItems.Selected)));
target.EndInit();
root.EndInit();
@ -1299,7 +1299,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var itemsBinding = new Binding("Child.Items");
var selectedIndBinding = new Binding("Child.SelectedIndex");
target.Bind(SelectingItemsControl.ItemsProperty, itemsBinding);
target.Bind(SelectingItemsControl.ItemsSourceProperty, itemsBinding);
target.Bind(SelectingItemsControl.SelectedIndexProperty, selectedIndBinding);
Assert.Equal(1, target.SelectedIndex);
@ -1318,7 +1318,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
@ -1336,7 +1336,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
@ -1359,7 +1359,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectedIndex = 1,
};
@ -1384,7 +1384,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectedIndex = 1,
};
@ -1405,14 +1405,14 @@ namespace Avalonia.Controls.UnitTests.Primitives
var other = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.AlwaysSelected,
};
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
[!ListBox.SelectedIndexProperty] = other[!ListBox.SelectedIndexProperty],
};
@ -1434,14 +1434,14 @@ namespace Avalonia.Controls.UnitTests.Primitives
var other = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.AlwaysSelected,
};
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
[!ListBox.SelectedItemProperty] = other[!ListBox.SelectedItemProperty],
};
@ -1468,7 +1468,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectedIndex = 1,
};
@ -1493,7 +1493,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
};
var raised = false;
@ -1518,7 +1518,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
};
var raised = false;
@ -1540,7 +1540,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<string>((x, _) =>
new TextBlock
{
@ -1582,7 +1582,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectedIndex = 2,
};
@ -1614,7 +1614,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectedIndex = 2,
};
@ -1645,7 +1645,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
AutoScrollToSelectedItem = false,
};
@ -1671,7 +1671,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.BeginInit();
target.Template = Template();
target.Items = new[] { "Foo", "Bar", "Baz" };
target.ItemsSource = new[] { "Foo", "Bar", "Baz" };
target.SelectedItems = selectedItems;
target.SelectedItem = "Bar";
target.EndInit();
@ -1691,7 +1691,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[]
Items =
{
new ListBoxItem { Focusable = false },
new ListBoxItem { Focusable = false },
@ -1713,7 +1713,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[]
Items =
{
new ListBoxItem(),
new ListBoxItem { Focusable = false },
@ -1733,7 +1733,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[]
Items =
{
new ListBoxItem { Focusable = false },
new ListBoxItem(),
@ -1753,7 +1753,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[]
Items =
{
new ListBoxItem { Focusable = false },
new ListBoxItem { Focusable = false },
@ -1776,7 +1776,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[]
Items =
{
new ListBoxItem { Focusable = false },
new ListBoxItem { Focusable = false },
@ -1800,7 +1800,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[]
Items =
{
new ListBoxItem(),
new ListBoxItem { IsEnabled = false },
@ -1831,7 +1831,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
second
};
target.Items = items;
target.ItemsSource = items;
Prepare(target);
@ -1878,7 +1878,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
[!ListBox.ItemsProperty] = new Binding("Items"),
[!ListBox.ItemsSourceProperty] = new Binding("Items"),
[!ListBox.SelectedIndexProperty] = new Binding("SelectedIndex"),
DataContext = vm,
};
@ -1906,7 +1906,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
[!ListBox.ItemsProperty] = new Binding("Items"),
[!ListBox.ItemsSourceProperty] = new Binding("Items"),
[!ListBox.SelectedItemsProperty] = new Binding("SelectedItems"),
DataContext = vm,
};
@ -1926,7 +1926,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
using var app = Start();
var target = new SelectingItemsControl
{
Items = new[] { "foo", "bar", "baz"},
ItemsSource = new[] { "foo", "bar", "baz"},
SelectedItem = "bar",
};
@ -1935,7 +1935,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(1, target.SelectedIndex);
Assert.Equal("bar", target.SelectedItem);
target.Items = new[] { "qux", "foo", "bar" };
target.ItemsSource = new[] { "qux", "foo", "bar" };
Assert.Equal(2, target.SelectedIndex);
Assert.Equal("bar", target.SelectedItem);
@ -1947,7 +1947,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
using var app = Start();
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
};
var raised = 0;
@ -1976,7 +1976,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
using var app = Start();
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
};
var raised = 0;
@ -2007,13 +2007,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
// Simulates problem with TabStrip and Carousel with bound SelectedIndex.
var tabStrip = new TestSelector
{
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.AlwaysSelected,
};
var carousel = new TestSelector
{
Items = items,
ItemsSource = items,
[!Carousel.SelectedIndexProperty] = tabStrip[!TabStrip.SelectedIndexProperty],
};
@ -2048,13 +2048,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
// Simulates problem with TabStrip and Carousel with bound SelectedItem.
var tabStrip = new TestSelector
{
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.AlwaysSelected,
};
var carousel = new TestSelector
{
Items = items,
ItemsSource = items,
[!Carousel.SelectedItemProperty] = tabStrip[!TabStrip.SelectedItemProperty],
};
@ -2100,7 +2100,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
IsTextSearchEnabled = false
};

12
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -32,7 +32,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = new AvaloniaList<string>();
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = new ResetOnAdd();
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -68,7 +68,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -87,7 +87,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
};
@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = new AvaloniaList<string>(new[] { "foo", "bar" });
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
};

231
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -27,7 +27,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -42,7 +42,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -57,7 +57,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -75,7 +75,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
Template = Template(),
};
@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
// Issue #2565.
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
Template = Template(),
};
@ -112,7 +112,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -129,7 +129,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -157,7 +157,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -179,7 +179,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -200,26 +200,23 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Adding_SelectedItems_Should_Set_Item_IsSelected()
{
var items = new[]
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
};
var target = new TestSelector
{
Items = items,
Items =
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
},
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedItems.Add(items[0]);
target.SelectedItems.Add(items[1]);
var foo = target.Presenter.Panel.Children[0];
target.SelectedItems.Add(target.Items[0]);
target.SelectedItems.Add(target.Items[1]);
var items = target.Items.Cast<ListBoxItem>().ToList();
Assert.True(items[0].IsSelected);
Assert.True(items[1].IsSelected);
Assert.False(items[2].IsSelected);
@ -228,23 +225,22 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Assigning_SelectedItems_Should_Set_Item_IsSelected()
{
var items = new[]
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
};
var target = new TestSelector
{
Items = items,
Items =
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
},
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedItems = new AvaloniaList<object> { items[0], items[1] };
target.SelectedItems = new AvaloniaList<object> { target.Items[0], target.Items[1] };
var items = target.Items.Cast<ListBoxItem>().ToList();
Assert.True(items[0].IsSelected);
Assert.True(items[1].IsSelected);
Assert.False(items[2].IsSelected);
@ -253,25 +249,24 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Removing_SelectedItems_Should_Clear_Item_IsSelected()
{
var items = new[]
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
};
var target = new TestSelector
{
Items = items,
Items =
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
},
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedItems.Add(items[0]);
target.SelectedItems.Add(items[1]);
target.SelectedItems.Remove(items[1]);
target.SelectedItems.Add(target.Items[0]);
target.SelectedItems.Add(target.Items[1]);
target.SelectedItems.Remove(target.Items[1]);
var items = target.Items.Cast<ListBoxItem>().ToList();
Assert.True(items[0].IsSelected);
Assert.False(items[1].IsSelected);
}
@ -279,25 +274,24 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Reassigning_SelectedItems_Should_Clear_Item_IsSelected()
{
var items = new[]
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
};
var target = new TestSelector
{
Items = items,
Items =
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
},
Template = Template(),
};
target.ApplyTemplate();
target.SelectedItems.Add(items[0]);
target.SelectedItems.Add(items[1]);
target.SelectedItems.Add(target.Items[0]);
target.SelectedItems.Add(target.Items[1]);
target.SelectedItems = new AvaloniaList<object> { items[0], items[1] };
target.SelectedItems = new AvaloniaList<object> { target.Items[0], target.Items[1] };
var items = target.Items.Cast<ListBoxItem>().ToList();
Assert.False(items[0].IsSelected);
Assert.False(items[1].IsSelected);
}
@ -307,7 +301,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
Template = Template(),
};
@ -329,7 +323,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[]
ItemsSource = new[]
{
"foo",
"bar",
@ -353,7 +347,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[]
ItemsSource = new[]
{
"foo",
"bar",
@ -378,7 +372,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[]
ItemsSource = new[]
{
"foo",
"bar",
@ -404,7 +398,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz", "qux" },
ItemsSource = new[] { "foo", "bar", "baz", "qux" },
Template = Template(),
SelectedIndex = 0,
SelectionMode = SelectionMode.Multiple,
@ -427,7 +421,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz", "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz", "foo", "bar", "baz" },
Template = Template(),
SelectedIndex = 0,
SelectionMode = SelectionMode.Multiple,
@ -450,7 +444,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
Template = Template(),
};
@ -471,7 +465,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
Template = Template(),
};
@ -536,8 +530,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
Mode = BindingMode.OneWay,
};
// Bind Items and SelectedItems to the VM.
target.Bind(TestSelector.ItemsProperty, itemsBinding);
// Bind ItemsSource and SelectedItems to the VM.
target.Bind(TestSelector.ItemsSourceProperty, itemsBinding);
target.Bind(TestSelector.SelectedItemsProperty, selectedItemsBinding);
// Set DataContext and SelectedIndex
@ -576,8 +570,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
Mode = BindingMode.OneWay,
};
// Bind Items and Selection to the VM.
target.Bind(TestSelector.ItemsProperty, itemsBinding);
// Bind ItemsSource and Selection to the VM.
target.Bind(TestSelector.ItemsSourceProperty, itemsBinding);
target.Bind(TestSelector.SelectionProperty, selectionBinding);
// Set DataContext and SelectedIndex
@ -610,9 +604,9 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
var itemsBinding = new Binding { Path = "Items" };
target.Bind(TestSelector.ItemsProperty, itemsBinding);
target.Bind(TestSelector.ItemsSourceProperty, itemsBinding);
Assert.Same(data.Items, target.Items);
Assert.Same(data.Items, target.ItemsSource);
target.SelectedItems.Add("bar");
target.DataContext = null;
@ -629,7 +623,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
DataContext = items,
Template = Template(),
Items = items,
ItemsSource = items,
};
var called = false;
@ -653,7 +647,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedItem = "bar",
};
@ -679,7 +673,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedItem = "bar",
};
@ -708,7 +702,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -733,7 +727,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Qux" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Qux" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -791,7 +785,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Qux" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Qux" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -825,7 +819,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -856,7 +850,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -884,7 +878,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -912,7 +906,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -940,7 +934,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Qux" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Qux" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -992,7 +986,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -1026,7 +1020,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
};
@ -1045,7 +1039,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
};
@ -1059,7 +1053,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.SelectAll();
Assert.NotNull(receivedArgs);
Assert.Equal(target.Items, receivedArgs.AddedItems);
Assert.Equal(target.ItemsSource, receivedArgs.AddedItems);
Assert.Empty(receivedArgs.RemovedItems);
}
@ -1069,7 +1063,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
SelectedIndex = 0,
};
@ -1089,7 +1083,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
};
@ -1113,7 +1107,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -1145,7 +1139,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.Multiple,
};
@ -1178,7 +1172,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -1209,7 +1203,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = items,
ItemsSource = items,
SelectionMode = SelectionMode.Multiple,
Width = 100,
Height = 100,
@ -1232,7 +1226,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
Width = 100,
@ -1262,7 +1256,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
Width = 100,
@ -1290,7 +1284,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
Width = 100,
@ -1315,27 +1309,25 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Adding_Selected_ItemContainers_Should_Update_Selection()
{
var items = new AvaloniaList<ItemContainer>(new[]
{
new ItemContainer(),
new ItemContainer(),
});
var target = new TestSelector
{
Items = items,
Items =
{
new ItemContainer(),
new ItemContainer(),
},
SelectionMode = SelectionMode.Multiple,
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
items.Add(new ItemContainer { IsSelected = true });
items.Add(new ItemContainer { IsSelected = true });
target.Items.Add(new ItemContainer { IsSelected = true });
target.Items.Add(new ItemContainer { IsSelected = true });
Assert.Equal(2, target.SelectedIndex);
Assert.Equal(items[2], target.SelectedItem);
Assert.Equal(new[] { items[2], items[3] }, target.SelectedItems);
Assert.Equal(target.Items[2], target.SelectedItem);
Assert.Equal(new[] { target.Items[2], target.Items[3] }, target.SelectedItems);
}
[Fact]
@ -1346,7 +1338,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
Width = 100,
@ -1373,7 +1365,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new ListBox
{
Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" },
ItemsSource = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
Width = 100,
@ -1397,7 +1389,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -1412,7 +1404,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -1429,7 +1421,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -1442,14 +1434,14 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
var selection = new SelectionModel<string>();
target.Selection = selection;
Assert.Same(target.Items, selection.Source);
Assert.Same(target.ItemsSource, selection.Source);
}
[Fact]
@ -1457,7 +1449,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -1478,7 +1470,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
ItemsSource = new[] { "foo", "bar", "baz" },
Template = Template(),
};
@ -1499,7 +1491,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestSelector
{
Items = new[] { "foo", "bar" },
ItemsSource = new[] { "foo", "bar" },
Template = Template(),
};
@ -1514,16 +1506,14 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void Assigning_Selection_Should_Set_Item_IsSelected()
{
var items = new[]
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
};
var target = new TestSelector
{
Items = items,
Items =
{
new ListBoxItem(),
new ListBoxItem(),
new ListBoxItem(),
},
Template = Template(),
};
@ -1534,6 +1524,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
selection.SelectRange(0, 1);
target.Selection = selection;
var items = target.Items.Cast<ListBoxItem>().ToList();
Assert.True(items[0].IsSelected);
Assert.True(items[1].IsSelected);
Assert.False(items[2].IsSelected);
@ -1546,7 +1537,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedItem = "bar",
};

26
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs

@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
@ -53,7 +53,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new ListBox
{
Items = items,
ItemsSource = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
@ -78,7 +78,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
@ -143,7 +143,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template()
};
@ -158,7 +158,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"
@ -176,7 +176,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"
@ -197,7 +197,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"
@ -205,7 +205,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Prepare(sic);
sic.Items = new List<TestClass>
sic.ItemsSource = new List<TestClass>
{
new TestClass("NewItem", string.Empty)
};
@ -224,7 +224,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
};
@ -250,7 +250,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var items = TestClass.GetItems();
var sic = new SelectingItemsControl
{
Items = items,
ItemsSource = items,
Template = Template(),
SelectedValueBinding = new Binding("Name"),
SelectedValue = "Item2"

94
tests/Avalonia.Controls.UnitTests/Primitives/TabStripTests.cs

@ -14,90 +14,84 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact]
public void First_Tab_Should_Be_Selected_By_Default()
{
var items = new[]
{
new TabItem
{
Name = "first"
},
new TabItem
{
Name = "second"
},
};
var target = new TabStrip
{
Template = new FuncControlTemplate<TabStrip>(CreateTabStripTemplate),
Items = items,
Items =
{
new TabItem
{
Name = "first"
},
new TabItem
{
Name = "second"
},
}
};
target.ApplyTemplate();
Assert.Equal(0, target.SelectedIndex);
Assert.Same(items[0], target.SelectedItem);
Assert.Same(target.Items[0], target.SelectedItem);
}
[Fact]
public void Setting_SelectedItem_Should_Set_Selection()
{
var items = new[]
{
new TabItem
{
Name = "first"
},
new TabItem
{
Name = "second"
},
};
var target = new TabStrip
{
Template = new FuncControlTemplate<TabStrip>(CreateTabStripTemplate),
Items = items,
SelectedItem = items[1],
Items =
{
new TabItem
{
Name = "first"
},
new TabItem
{
Name = "second"
},
},
};
target.SelectedItem = target.Items[1];
target.ApplyTemplate();
Assert.Equal(1, target.SelectedIndex);
Assert.Same(items[1], target.SelectedItem);
Assert.Same(target.Items[1], target.SelectedItem);
}
[Fact]
public void Removing_Selected_Should_Select_First()
{
var items = new ObservableCollection<TabItem>()
{
new TabItem
{
Name = "first"
},
new TabItem
{
Name = "second"
},
new TabItem
{
Name = "3rd"
},
};
var target = new TabStrip
{
Template = new FuncControlTemplate<TabStrip>(CreateTabStripTemplate),
Items = items
Items =
{
new TabItem
{
Name = "first"
},
new TabItem
{
Name = "second"
},
new TabItem
{
Name = "3rd"
},
}
};
target.ApplyTemplate();
target.SelectedItem = items[1];
Assert.Same(items[1], target.SelectedItem);
items.RemoveAt(1);
target.SelectedItem = target.Items[1];
Assert.Same(target.Items[1], target.SelectedItem);
target.Items.RemoveAt(1);
Assert.Equal(0, target.SelectedIndex);
Assert.Same(items[0], target.SelectedItem);
Assert.Same(target.Items[0], target.SelectedItem);
Assert.Same("first", ((TabItem)target.SelectedItem).Name);
}

220
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -24,7 +24,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl
{
Template = TabControlTemplate(),
Items = new[]
Items =
{
(selected = new TabItem
{
@ -48,21 +48,17 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Pre_Selecting_TabItem_Should_Set_SelectedContent_After_It_Was_Added()
{
const string secondContent = "Second";
var target = new TabControl
{
Template = TabControlTemplate(),
Items =
{
new TabItem { Header = "First"},
new TabItem { Header = "Second", Content = secondContent, IsSelected = true }
},
};
const string secondContent = "Second";
var items = new AvaloniaList<object>
{
new TabItem { Header = "First"},
new TabItem { Header = "Second", Content = secondContent, IsSelected = true }
};
target.Items = items;
ApplyTemplate(target);
Assert.Equal(secondContent, target.SelectedContent);
@ -71,107 +67,105 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Logical_Children_Should_Be_TabItems()
{
var items = new[]
{
new TabItem
{
Content = "foo"
},
new TabItem
{
Content = "bar"
},
};
var target = new TabControl
{
Template = TabControlTemplate(),
Items = items,
Items =
{
new TabItem
{
Content = "foo"
},
new TabItem
{
Content = "bar"
},
}
};
Assert.Equal(items, target.GetLogicalChildren());
Assert.Equal(target.Items, target.GetLogicalChildren().ToList());
target.ApplyTemplate();
Assert.Equal(items, target.GetLogicalChildren());
Assert.Equal(target.Items, target.GetLogicalChildren().ToList());
}
[Fact]
public void Removal_Should_Set_First_Tab()
{
var collection = new ObservableCollection<TabItem>()
{
new TabItem
{
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
};
var target = new TabControl
{
Template = TabControlTemplate(),
Items = collection,
Items =
{
new TabItem
{
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
}
};
Prepare(target);
target.SelectedItem = collection[1];
target.SelectedItem = target.Items[1];
Assert.Same(collection[1], target.SelectedItem);
Assert.Equal(collection[1].Content, target.SelectedContent);
var item = Assert.IsType<TabItem>(target.Items[1]);
Assert.Same(item, target.SelectedItem);
Assert.Equal(item.Content, target.SelectedContent);
collection.RemoveAt(1);
target.Items.RemoveAt(1);
Assert.Same(collection[0], target.SelectedItem);
Assert.Equal(collection[0].Content, target.SelectedContent);
item = Assert.IsType<TabItem>(target.Items[0]);
Assert.Same(item, target.SelectedItem);
Assert.Equal(item.Content, target.SelectedContent);
}
[Fact]
public void Removal_Should_Set_New_Item0_When_Item0_Selected()
{
var collection = new ObservableCollection<TabItem>()
{
new TabItem
{
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
};
var target = new TabControl
{
Template = TabControlTemplate(),
Items = collection,
Items =
{
new TabItem
{
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
}
};
Prepare(target);
target.SelectedItem = collection[0];
target.SelectedItem = target.Items[0];
Assert.Same(collection[0], target.SelectedItem);
Assert.Equal(collection[0].Content, target.SelectedContent);
var item = Assert.IsType<TabItem>(target.Items[0]);
Assert.Same(item, target.SelectedItem);
Assert.Equal(item.Content, target.SelectedContent);
collection.RemoveAt(0);
target.Items.RemoveAt(0);
Assert.Same(collection[0], target.SelectedItem);
Assert.Equal(collection[0].Content, target.SelectedContent);
item = Assert.IsType<TabItem>(target.Items[0]);
Assert.Same(item, target.SelectedItem);
Assert.Equal(item.Content, target.SelectedContent);
}
[Fact]
@ -189,7 +183,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl
{
Template = TabControlTemplate(),
Items = collection,
ItemsSource = collection,
};
Prepare(target);
@ -207,26 +201,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void TabItem_Templates_Should_Be_Set_Before_TabItem_ApplyTemplate()
{
var collection = new[]
{
new TabItem
{
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
};
var template = new FuncControlTemplate<TabItem>((x, __) => new Decorator());
TabControl target;
var root = new TestRoot
{
Styles =
@ -239,13 +215,31 @@ namespace Avalonia.Controls.UnitTests
}
}
},
Child = new TabControl
Child = (target = new TabControl
{
Template = TabControlTemplate(),
Items = collection,
}
Items =
{
new TabItem
{
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
},
})
};
var collection = target.Items.Cast<TabItem>().ToList();
Assert.Same(collection[0].Template, template);
Assert.Same(collection[1].Template, template);
Assert.Same(collection[2].Template, template);
@ -271,7 +265,7 @@ namespace Avalonia.Controls.UnitTests
{
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
},
Items = items,
ItemsSource = items,
};
ApplyTemplate(target);
@ -313,16 +307,14 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Non_IHeadered_Control_Items_Should_Be_Ignored()
{
var items = new[]
{
new TextBlock { Text = "foo" },
new TextBlock { Text = "bar" },
};
var target = new TabControl
{
Template = TabControlTemplate(),
Items = items,
Items =
{
new TextBlock { Text = "foo" },
new TextBlock { Text = "bar" },
},
};
ApplyTemplate(target);
@ -343,7 +335,7 @@ namespace Avalonia.Controls.UnitTests
TabControl target = new TabControl
{
Template = TabControlTemplate(),
Items = new[]
Items =
{
new TabItem { Header = "Foo" },
new TabItem { Header = "Foo", Content = new Decorator() },
@ -368,7 +360,7 @@ namespace Avalonia.Controls.UnitTests
Template = TabControlTemplate(),
ContentTemplate = new FuncDataTemplate<string>((x, _) =>
new TextBlock { Tag = "bar", Text = x }),
Items = new[] { "Foo" },
ItemsSource = new[] { "Foo" },
};
var root = new TestRoot(target);
@ -392,7 +384,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = TabControlTemplate(),
DataContext = dataContext,
Items = new AvaloniaList<object> { tabItem }
Items = { tabItem }
};
ApplyTemplate(target);
@ -409,7 +401,7 @@ namespace Avalonia.Controls.UnitTests
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TabControl Name='tabs' Items='{Binding Tabs}'/>
<TabControl Name='tabs' ItemsSource='{Binding Tabs}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var tabControl = window.FindControl<TabControl>("tabs");
@ -417,7 +409,7 @@ namespace Avalonia.Controls.UnitTests
tabControl.DataContext = new { Tabs = new List<string>() };
window.ApplyTemplate();
Assert.Equal(0, tabControl.Items.Count());
Assert.Equal(0, tabControl.ItemsSource.Count());
}
}

2
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@ -192,7 +192,7 @@ namespace Avalonia.Controls.UnitTests
target.Inlines.Add(new Run("Hello World"));
Assert.Equal("Hello World", target.Text);
Assert.Equal(null, target.Text);
Assert.Equal(1, target.Inlines.Count);

118
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -31,7 +31,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
ItemsSource = CreateTestTreeData(),
};
var root = new TestRoot(target);
@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests
Child = target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
ItemsSource = CreateTestTreeData(),
ItemTemplate = new FuncTreeDataTemplate<Node>(
(_, __) => new Canvas(),
x => x.Children),
@ -82,7 +82,7 @@ namespace Avalonia.Controls.UnitTests
Child = target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
ItemsSource = CreateTestTreeData(),
ItemContainerTheme = theme,
ItemTemplate = new FuncTreeDataTemplate<Node>(
(_, __) => new Canvas(),
@ -112,7 +112,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
ItemsSource = CreateTestTreeData(),
};
var root = new TestRoot(target);
@ -131,7 +131,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
ItemsSource = CreateTestTreeData(),
};
var root = new TestRoot(target);
@ -151,7 +151,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
// For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs
@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -212,7 +212,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree
ItemsSource = tree
};
var visualRoot = new TestRoot();
@ -247,7 +247,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree
ItemsSource = tree
};
var visualRoot = new TestRoot();
@ -287,7 +287,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple
};
@ -330,7 +330,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple
};
@ -367,7 +367,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple
};
@ -404,7 +404,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple
};
@ -455,7 +455,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -488,7 +488,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -521,7 +521,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -554,7 +554,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -591,7 +591,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -649,7 +649,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -686,7 +686,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -744,7 +744,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -781,7 +781,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -839,7 +839,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -876,7 +876,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -934,7 +934,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -985,7 +985,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -1036,7 +1036,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -1066,7 +1066,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -1105,7 +1105,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = dataContext
};
target.Bind(TreeView.ItemsProperty, new Binding("Items"));
target.Bind(TreeView.ItemsSourceProperty, new Binding("Items"));
target.Bind(TreeView.SelectedItemProperty, new Binding("SelectedItem"));
var visualRoot = new TestRoot();
@ -1137,7 +1137,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = new[] { "Foo", "Bar", "Baz " },
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
ApplyTemplates(target);
@ -1171,7 +1171,7 @@ namespace Avalonia.Controls.UnitTests
{
new FuncDataTemplate<Node>((x, _) => new Button { Content = x })
},
Items = items,
ItemsSource = items,
};
ApplyTemplates(target);
@ -1189,15 +1189,13 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Control_Item_Should_Not_Be_NameScope()
{
var items = new object[]
{
new TreeViewItem(),
};
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = items,
Items =
{
new TreeViewItem(),
}
};
target.ApplyTemplate();
@ -1215,7 +1213,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = data,
ItemsSource = data,
};
var root = new TestRoot(target);
@ -1256,7 +1254,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = data,
ItemsSource = data,
};
var button = new Button();
@ -1302,7 +1300,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = data,
ItemsSource = data,
SelectedItem = selectedNode
};
@ -1341,7 +1339,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple
};
@ -1379,7 +1377,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple
};
@ -1426,7 +1424,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple
};
@ -1473,7 +1471,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
@ -1503,7 +1501,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple,
};
@ -1542,7 +1540,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple,
};
@ -1575,7 +1573,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
SelectionMode = SelectionMode.Multiple,
};
@ -1606,7 +1604,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -1630,7 +1628,7 @@ namespace Avalonia.Controls.UnitTests
var target = new DerivedTreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -1655,7 +1653,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var visualRoot = new TestRoot();
@ -1688,11 +1686,11 @@ namespace Avalonia.Controls.UnitTests
{
Template = CreateTreeViewTemplate(),
SelectionMode = SelectionMode.Multiple,
Items = new List<Node>
{
new Node { Value = "Root1", },
new Node { Value = "Root2", },
},
ItemsSource = new List<Node>
{
new Node { Value = "Root1", },
new Node { Value = "Root2", },
},
};
var visualRoot = new TestRoot
@ -1726,7 +1724,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var root = new TestRoot();
@ -1760,7 +1758,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
var root = new TestRoot();
@ -1788,7 +1786,7 @@ namespace Avalonia.Controls.UnitTests
var target = new DerivedTreeViewWithDerivedTreeViewItems
{
Template = CreateTreeViewTemplate(),
Items = tree,
ItemsSource = tree,
};
ApplyTemplates(target);
@ -1825,14 +1823,14 @@ namespace Avalonia.Controls.UnitTests
foreach (var index in indexes)
{
var item = ((IList)c.Items)[index];
var item = c.ItemsView[index];
c = (ItemsControl)target.TreeContainerFromItem(item);
}
return (TreeViewItem)c;
}
private IList<Node> CreateTestTreeData()
private AvaloniaList<Node> CreateTestTreeData()
{
return new AvaloniaList<Node>
{

4
tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -263,7 +263,7 @@ namespace Avalonia.Controls.UnitTests.Utils
};
var rootMenu = new Menu();
rootMenu.Items = new[] { menuitem };
rootMenu.Items.Add(menuitem);
root.Content = rootMenu;
return menuitem;
@ -288,7 +288,7 @@ namespace Avalonia.Controls.UnitTests.Utils
};
var rootMenu = new Menu();
rootMenu.Items = new[] { menuitem };
rootMenu.Items.Add(menuitem);
root.Content = rootMenu;
return menuitem;

2
tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs

@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests
{
var carousel = new Carousel
{
Items = items,
ItemsSource = items,
Template = CarouselTemplate(),
PageTransition = transition,
};

15
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -9,15 +9,12 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
#nullable enable
namespace Avalonia.Controls.UnitTests
{
public class VirtualizingStackPanelTests
@ -53,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Empty(itemsControl.GetRealizedContainers());
itemsControl.Items = new[] { "foo", "bar" };
itemsControl.ItemsSource = new[] { "foo", "bar" };
Layout(target);
AssertRealizedItems(target, itemsControl, 0, 2);
@ -99,7 +96,7 @@ namespace Avalonia.Controls.UnitTests
{
using var app = App();
var (target, _, itemsControl) = CreateTarget();
var items = (IList)itemsControl.Items!;
var items = (IList)itemsControl.ItemsSource!;
Assert.Equal(10, target.GetRealizedElements().Count);
@ -131,7 +128,7 @@ namespace Avalonia.Controls.UnitTests
{
using var app = App();
var (target, _, itemsControl) = CreateTarget();
var items = (IList)itemsControl.Items!;
var items = (IList)itemsControl.ItemsSource!;
Assert.Equal(10, target.GetRealizedElements().Count);
@ -161,7 +158,7 @@ namespace Avalonia.Controls.UnitTests
{
using var app = App();
var (target, _, itemsControl) = CreateTarget();
var items = (ObservableCollection<string>)itemsControl.Items!;
var items = (ObservableCollection<string>)itemsControl.ItemsSource!;
Assert.Equal(10, target.GetRealizedElements().Count);
@ -190,7 +187,7 @@ namespace Avalonia.Controls.UnitTests
{
using var app = App();
var (target, _, itemsControl) = CreateTarget();
var items = (ObservableCollection<string>)itemsControl.Items!;
var items = (ObservableCollection<string>)itemsControl.ItemsSource!;
Assert.Equal(10, target.GetRealizedElements().Count);
@ -473,7 +470,7 @@ namespace Avalonia.Controls.UnitTests
var itemsControl = new ItemsControl
{
Items = items,
ItemsSource = items,
Template = new FuncControlTemplate<ItemsControl>((_, _) => scroll),
ItemsPanel = new FuncTemplate<Panel>(() => target),
};

14
tests/Avalonia.LeakTests/ControlTests.cs

@ -353,7 +353,7 @@ namespace Avalonia.LeakTests
(x, _) => new TextBlock { Text = x.Name },
x => x.Children)
},
Items = nodes
ItemsSource = nodes
}
};
@ -428,7 +428,7 @@ namespace Avalonia.LeakTests
{
Content = new TabControl
{
Items = new[] { new TabItem() }
ItemsSource = new[] { new TabItem() }
}
};
@ -440,7 +440,7 @@ namespace Avalonia.LeakTests
Assert.IsType<TabItem>(tabControl.Presenter.Panel.Children[0]);
// Clear the items and ensure the TabItem is removed.
tabControl.Items = null;
tabControl.ItemsSource = null;
window.LayoutManager.ExecuteLayoutPass();
Assert.Empty(tabControl.Presenter.Panel.Children);
@ -545,7 +545,7 @@ namespace Avalonia.LeakTests
{
var contextMenu = new ContextMenu
{
Items = new[]
Items =
{
new MenuItem { Header = "Foo" },
new MenuItem { Header = "Foo" },
@ -594,7 +594,7 @@ namespace Avalonia.LeakTests
{
var contextMenu = new ContextMenu
{
Items = new[]
Items =
{
new MenuItem { Header = "Foo" },
new MenuItem { Header = "Foo" },
@ -740,7 +740,7 @@ namespace Avalonia.LeakTests
}),
(lb = new ListBox
{
Items = items,
ItemsSource = items,
ItemTemplate = new FuncDataTemplate<int>((_, _) =>
new Canvas
{
@ -858,7 +858,7 @@ namespace Avalonia.LeakTests
// Add the listbox and render it
tl.Content = listBox;
lm.ExecuteInitialLayoutPass();
listBox.Items = keyGestures;
listBox.ItemsSource = keyGestures;
lm.ExecuteLayoutPass();
// Let the button detach when clearing the source items

8
tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs

@ -173,11 +173,11 @@ namespace Avalonia.Markup.UnitTests.Data
},
};
target.Bind(ItemsControl.ItemsProperty, binding);
target.Bind(ItemsControl.ItemsSourceProperty, binding);
Assert.Equal(target.ItemCount, 3);
Assert.Equal(target.Items.ElementAt(0), source.A);
Assert.Equal(target.Items.ElementAt(1), source.B);
Assert.Equal(target.Items.ElementAt(2), source.C);
Assert.Equal(target.ItemsView[0], source.A);
Assert.Equal(target.ItemsView[1], source.B);
Assert.Equal(target.ItemsView[2], source.C);
}
private class ConcatConverter : IMultiValueConverter

13
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -122,7 +122,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
<ItemsControl Name='itemsControl' Items='{CompiledBinding Path=ListProperty}'>
<ItemsControl Name='itemsControl' ItemsSource='{CompiledBinding Path=ListProperty}'>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock />
@ -143,7 +143,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.DataContext = dataContext;
Assert.Equal(dataContext.ListProperty, textBlock.Items);
Assert.Equal(dataContext.ListProperty, textBlock.ItemsSource);
}
}
@ -528,7 +528,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
<ItemsControl Items='{CompiledBinding ListProperty}' Name='target'>
<ItemsControl ItemsSource='{CompiledBinding ListProperty}' Name='target'>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text='{CompiledBinding}' Name='textBlock' />
@ -1782,7 +1782,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'
x:CompileBindings='True'>
<ComboBox x:Name='comboBox' Items='{Binding GenericProperty}' SelectedItem='{Binding GenericProperty.CurrentItem}' />
<ComboBox x:Name='comboBox' ItemsSource='{Binding GenericProperty}' SelectedItem='{Binding GenericProperty.CurrentItem}' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var comboBox = window.FindControl<ComboBox>("comboBox");
@ -1991,7 +1991,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public class DataGridLikeControl : Control
{
public static readonly DirectProperty<DataGridLikeControl, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<DataGridLikeControl>(o => o.Items, (o, v) => o.Items = v);
AvaloniaProperty.RegisterDirect<DataGridLikeControl, IEnumerable?>(
nameof(Items),
x => x.Items,
(x, v) => x.Items = v);
private IEnumerable _items;
public IEnumerable Items

8
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -505,7 +505,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'>
<ItemsControl Name='itemsControl' Items='{Binding}'>
<ItemsControl Name='itemsControl' ItemsSource='{Binding}'>
</ItemsControl>
</Window>
";
@ -522,7 +522,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
target.DataContext = items;
Assert.Equal(items, itemsControl.Items);
Assert.Equal(items, itemsControl.ItemsSource);
}
}
@ -760,7 +760,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'>
<ListBox Items='{Binding Items}' SelectedItems='{Binding SelectedItems}'/>
<ListBox ItemsSource='{Binding Items}' SelectedItems='{Binding SelectedItems}'/>
</Window>";
var window = AvaloniaRuntimeXamlLoader.Parse<Window>(xaml);
@ -773,7 +773,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
window.DataContext = vm;
Assert.Equal(vm.Items, listBox.Items);
Assert.Equal(vm.Items, listBox.ItemsSource);
Assert.Equal(vm.SelectedItems, listBox.SelectedItems);
}

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@ -74,7 +74,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=mscorlib'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ItemsControl Name='itemsControl' Items='{Binding}'>
<ItemsControl Name='itemsControl' ItemsSource='{Binding}'>
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl Name='foo'/>

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -377,7 +377,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
};
var list = window.FindControl<ListBox>("list");
list.Items = collection;
list.ItemsSource = collection;
window.Show();

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -391,7 +391,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
</ItemsControl>
</Window>
", typeof(XamlIlBugTestsEventHandlerCodeBehind).Assembly, this);
((ItemsControl)Content).Items = new[] {"123"};
((ItemsControl)Content).ItemsSource = new[] {"123"};
}
}

2
tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs

@ -41,7 +41,7 @@ namespace Avalonia.ReactiveUI.UnitTests
Content = List;
ViewModel = new ExampleViewModel();
this.OneWayBind(ViewModel, x => x.Items, x => x.List.Items);
this.OneWayBind(ViewModel, x => x.Items, x => x.List.ItemsSource);
}
}

Loading…
Cancel
Save