Browse Source

Deprecate IStyleable .

This was signalled for removal in #9553, but I was hesitant to do it because it will be in use by a lot of code in order to override `StyleKey`. Instead of removing it, deprecate it and provide a virtual `StyledElement.StyleKeyOverride` property as the supported way of overriding a control's style key.
pull/11380/head
Steven Kirk 3 years ago
parent
commit
474d78b335
  1. 60
      src/Avalonia.Base/StyledElement.cs
  2. 2
      src/Avalonia.Base/Styling/ControlTheme.cs
  3. 4
      src/Avalonia.Base/Styling/DescendentSelector.cs
  4. 4
      src/Avalonia.Base/Styling/IStyleable.cs
  5. 2
      src/Avalonia.Base/Styling/NestingSelector.cs
  6. 4
      src/Avalonia.Base/Styling/Selectors.cs
  7. 2
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  8. 4
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  9. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  10. 2
      src/Avalonia.Controls/ItemsControl.cs
  11. 4
      src/Avalonia.Controls/MaskedTextBox.cs
  12. 4
      src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs
  13. 2
      src/Avalonia.Controls/UserControl.cs
  14. 4
      src/Avalonia.Controls/Window.cs
  15. 4
      src/Avalonia.Diagnostics/Diagnostics/Controls/CommitTextBox.cs
  16. 4
      src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs
  17. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  18. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  19. 4
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  20. 4
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  21. 4
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  22. 8
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  23. 4
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  24. 4
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  25. 56
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

60
src/Avalonia.Base/StyledElement.cs

@ -30,10 +30,12 @@ namespace Avalonia
ILogical,
IThemeVariantHost,
IStyleHost,
IStyleable,
ISetLogicalParent,
ISetInheritanceParent,
ISupportInitialize
ISupportInitialize,
#pragma warning disable CS0618 // Type or member is obsolete
IStyleable
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.
@ -217,6 +219,18 @@ namespace Avalonia
/// </remarks>
public Styles Styles => _styles ??= new Styles(this);
/// <summary>
/// Gets the type by which the element is styled.
/// </summary>
/// <remarks>
/// Usually controls are styled by their own type, but there are instances where you want
/// an element to be styled by its base type, e.g. creating SpecialButton that
/// derives from Button and adds extra functionality but is still styled as a regular
/// Button. To change the style for a control class, override the <see cref="StyleKeyOverride"/>
/// property
/// </remarks>
public Type StyleKey => StyleKeyOverride;
/// <summary>
/// Gets or sets the styled element's resource dictionary.
/// </summary>
@ -278,6 +292,18 @@ namespace Avalonia
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <summary>
/// Gets the type by which the element is styled.
/// </summary>
/// <remarks>
/// Usually controls are styled by their own type, but there are instances where you want
/// an element to be styled by its base type, e.g. creating SpecialButton that
/// derives from Button and adds extra functionality but is still styled as a regular
/// Button. Override this property to change the style for a control class, returning the
/// type that you wish the elements to be styled as.
/// </remarks>
protected virtual Type StyleKeyOverride => GetType();
/// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary>
@ -309,24 +335,12 @@ namespace Avalonia
/// <inheritdoc/>
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
/// <summary>
/// Gets the type by which the styled element is styled.
/// </summary>
/// <remarks>
/// Usually controls are styled by their own type, but there are instances where you want
/// a styled element to be styled by its base type, e.g. creating SpecialButton that
/// derives from Button and adds extra functionality but is still styled as a regular
/// Button.
/// </remarks>
Type IStyleable.StyleKey => GetType();
/// <inheritdoc/>
bool IStyleHost.IsStylesInitialized => _styles != null;
/// <inheritdoc/>
IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent;
/// <inheritdoc/>
public virtual void BeginInit()
{
@ -669,7 +683,7 @@ namespace Avalonia
// If the Theme property is not set, try to find a ControlTheme resource with our StyleKey.
if (_implicitTheme is null)
{
var key = ((IStyleable)this).StyleKey;
var key = GetStyleKey(this);
if (this.TryFindResource(key, out var value) && value is ControlTheme t)
_implicitTheme = t;
@ -700,6 +714,22 @@ namespace Avalonia
}
}
/// <summary>
/// Internal getter for <see cref="IStyleable.StyleKey"/> so that we only need to suppress the obsolete
/// warning in one place.
/// </summary>
/// <param name="e">The element</param>
/// <remarks>
/// <see cref="IStyleable"/> is obsolete and will be removed in a future version, but for backwards
/// compatibility we need to support code which overrides <see cref="IStyleable.StyleKey"/>.
/// </remarks>
internal static Type GetStyleKey(StyledElement e)
{
#pragma warning disable CS0618 // Type or member is obsolete
return ((IStyleable)e).StyleKey;
#pragma warning restore CS0618 // Type or member is obsolete
}
private static void DataContextNotifying(AvaloniaObject o, bool updateStarted)
{
if (o is StyledElement element)

2
src/Avalonia.Base/Styling/ControlTheme.cs

@ -46,7 +46,7 @@ namespace Avalonia.Styling
if (TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
if (HasSettersOrAnimations && TargetType.IsAssignableFrom(((IStyleable)target).StyleKey))
if (HasSettersOrAnimations && TargetType.IsAssignableFrom(StyledElement.GetStyleKey(target)))
{
Attach(target, null, type);
return SelectorMatchResult.AlwaysThisType;

4
src/Avalonia.Base/Styling/DescendentSelector.cs

@ -44,9 +44,9 @@ namespace Avalonia.Styling
{
c = c.LogicalParent;
if (c is IStyleable)
if (c is StyledElement s)
{
var match = _parent.Match((StyledElement)c, parent, subscribe);
var match = _parent.Match(s, parent, subscribe);
if (match.Result == SelectorMatchResult.Sometimes)
{

4
src/Avalonia.Base/Styling/IStyleable.cs

@ -1,13 +1,12 @@
using System;
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Styling
{
/// <summary>
/// Interface for styleable elements.
/// </summary>
[NotClientImplementable]
[Obsolete("This interface may be removed in 12.0. Use StyledElement, or override StyledElement.StyleKeyOverride to override the StyleKey for a class.")]
public interface IStyleable : INamed
{
/// <summary>
@ -18,6 +17,7 @@ namespace Avalonia.Styling
/// <summary>
/// Gets the type by which the control is styled.
/// </summary>
[Obsolete("Override StyledElement.StyleKeyOverride instead.")]
Type StyleKey { get; }
/// <summary>

2
src/Avalonia.Base/Styling/NestingSelector.cs

@ -23,7 +23,7 @@ namespace Avalonia.Styling
{
if (theme.TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
return theme.TargetType.IsAssignableFrom(((IStyleable)control).StyleKey) ?
return theme.TargetType.IsAssignableFrom(StyledElement.GetStyleKey(control)) ?
SelectorMatch.AlwaysThisType :
SelectorMatch.NeverThisType;
}

4
src/Avalonia.Base/Styling/Selectors.cs

@ -76,7 +76,7 @@ namespace Avalonia.Styling
/// <typeparam name="T">The type.</typeparam>
/// <param name="previous">The previous selector.</param>
/// <returns>The selector.</returns>
public static Selector Is<T>(this Selector? previous) where T : IStyleable
public static Selector Is<T>(this Selector? previous) where T : StyledElement
{
return previous.Is(typeof(T));
}
@ -171,7 +171,7 @@ namespace Avalonia.Styling
/// <typeparam name="T">The type.</typeparam>
/// <param name="previous">The previous selector.</param>
/// <returns>The selector.</returns>
public static Selector OfType<T>(this Selector? previous) where T : IStyleable
public static Selector OfType<T>(this Selector? previous) where T : StyledElement
{
return previous.OfType(typeof(T));
}

2
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -93,7 +93,7 @@ namespace Avalonia.Styling
{
if (TargetType != null)
{
var controlType = ((IStyleable)control).StyleKey ?? control.GetType();
var controlType = StyledElement.GetStyleKey(control) ?? control.GetType();
if (IsConcreteType)
{

4
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -7,7 +7,7 @@ using Avalonia.Styling;
namespace Avalonia.Controls.Embedding
{
public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, IDisposable
public class EmbeddableControlRoot : TopLevel, IFocusScope, IDisposable
{
public EmbeddableControlRoot(ITopLevelImpl impl) : base(impl)
{
@ -46,7 +46,7 @@ namespace Avalonia.Controls.Embedding
return rv;
}
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
protected override Type StyleKeyOverride => typeof(EmbeddableControlRoot);
public void Dispose() => PlatformImpl?.Dispose();
}
}

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@ -4,7 +4,7 @@ using Avalonia.Styling;
namespace Avalonia.Controls.Embedding.Offscreen
{
class OffscreenTopLevel : TopLevel, IStyleable
class OffscreenTopLevel : TopLevel
{
public OffscreenTopLevelImplBase Impl { get; }
@ -31,7 +31,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
}
}
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
protected override Type StyleKeyOverride => typeof(EmbeddableControlRoot);
public void Dispose()
{
PlatformImpl?.Dispose();

2
src/Avalonia.Controls/ItemsControl.cs

@ -712,7 +712,7 @@ namespace Avalonia.Controls
if (itemContainerTheme is not null &&
!container.IsSet(ThemeProperty) &&
((IStyleable)container).StyleKey == itemContainerTheme.TargetType)
StyledElement.GetStyleKey(container) == itemContainerTheme.TargetType)
{
container.Theme = itemContainerTheme;
}

4
src/Avalonia.Controls/MaskedTextBox.cs

@ -10,7 +10,7 @@ using Avalonia.Styling;
namespace Avalonia.Controls
{
public class MaskedTextBox : TextBox, IStyleable
public class MaskedTextBox : TextBox
{
public static readonly StyledProperty<bool> AsciiOnlyProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(AsciiOnly));
@ -183,7 +183,7 @@ namespace Avalonia.Controls
set => SetValue(ResetOnSpaceProperty, value);
}
Type IStyleable.StyleKey => typeof(TextBox);
protected override Type StyleKeyOverride => typeof(TextBox);
/// <inheritdoc />
protected override void OnGotFocus(GotFocusEventArgs e)

4
src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls
/// the secondary part opens a flyout.
/// </summary>
[PseudoClasses(pcChecked)]
public class ToggleSplitButton : SplitButton, IStyleable
public class ToggleSplitButton : SplitButton
{
/// <summary>
/// Raised when the <see cref="IsChecked"/> property value changes.
@ -63,7 +63,7 @@ namespace Avalonia.Controls
/// Both <see cref="ToggleSplitButton"/> and <see cref="SplitButton"/> share
/// the same exact default style.
/// </remarks>
Type IStyleable.StyleKey => typeof(SplitButton);
protected override Type StyleKeyOverride => typeof(SplitButton);
/// <summary>
/// Toggles the <see cref="IsChecked"/> property between true and false.

2
src/Avalonia.Controls/UserControl.cs

@ -5,7 +5,7 @@ namespace Avalonia.Controls
/// <summary>
/// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic.
/// </summary>
public class UserControl : ContentControl, IStyleable
public class UserControl : ContentControl
{
}

4
src/Avalonia.Controls/Window.cs

@ -65,7 +65,7 @@ namespace Avalonia.Controls
/// <summary>
/// A top-level window.
/// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
public class Window : WindowBase, IFocusScope, ILayoutRoot
{
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
private bool _isExtendedIntoWindowDecorations;
@ -420,7 +420,7 @@ namespace Avalonia.Controls
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e);
/// <inheritdoc/>
Type IStyleable.StyleKey => typeof(Window);
protected override Type StyleKeyOverride => typeof(Window);
/// <summary>
/// Fired before a window is closed.

4
src/Avalonia.Diagnostics/Diagnostics/Controls/CommitTextBox.cs

@ -7,9 +7,9 @@ using Avalonia.Styling;
namespace Avalonia.Diagnostics.Controls
{
//TODO: UpdateSourceTrigger & Binding.ValidationRules could help removing the need for this control.
internal sealed class CommitTextBox : TextBox, IStyleable
internal sealed class CommitTextBox : TextBox
{
Type IStyleable.StyleKey => typeof(TextBox);
protected override Type StyleKeyOverride => typeof(TextBox);
/// <summary>
/// Defines the <see cref="CommittedText" /> property.

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

@ -5,7 +5,7 @@ using Avalonia.Styling;
namespace Avalonia.Diagnostics.Controls
{
internal class FilterTextBox : TextBox, IStyleable
internal class FilterTextBox : TextBox
{
public static readonly StyledProperty<bool> UseRegexFilterProperty =
AvaloniaProperty.Register<FilterTextBox, bool>(nameof(UseRegexFilter),
@ -42,6 +42,6 @@ namespace Avalonia.Diagnostics.Controls
set => SetValue(UseWholeWordFilterProperty, value);
}
Type IStyleable.StyleKey => typeof(TextBox);
protected override Type StyleKeyOverride => typeof(TextBox);
}
}

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -208,7 +208,7 @@ namespace Avalonia.Diagnostics.ViewModels
var classes = string.Concat(visual.Classes
.Where(c => !c.StartsWith(":"))
.Select(c => '.' + c));
var typeName = ((IStyleable)visual).StyleKey.Name;
var typeName = StyledElement.GetStyleKey(visual);
return $"{typeName}{name}{classes}";
}

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@ -23,7 +23,7 @@ namespace Avalonia.Diagnostics.ViewModels
_ => TreeNodeCollection.Empty
};
if (Visual is IStyleable styleable)
if (Visual is StyledElement styleable)
IsInTemplate = styleable.TemplatedParent != null;
}

4
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -50,7 +50,7 @@ namespace Avalonia.ReactiveUI
/// ReactiveUI routing documentation website</see> for more info.
/// </para>
/// </remarks>
public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger, IStyleable
public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.
@ -126,7 +126,7 @@ namespace Avalonia.ReactiveUI
/// </summary>
public IViewLocator? ViewLocator { get; set; }
Type IStyleable.StyleKey => typeof(TransitioningContentControl);
protected override Type StyleKeyOverride => typeof(TransitioningContentControl);
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.

4
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@ -13,7 +13,7 @@ namespace Avalonia.ReactiveUI
/// the ViewModel property and display it. This control is very useful
/// inside a DataTemplate to display the View associated with a ViewModel.
/// </summary>
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IStyleable
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewModel"/> property.
@ -78,7 +78,7 @@ namespace Avalonia.ReactiveUI
/// </summary>
public IViewLocator? ViewLocator { get; set; }
Type IStyleable.StyleKey => typeof(TransitioningContentControl);
protected override Type StyleKeyOverride => typeof(TransitioningContentControl);
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.

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

@ -170,9 +170,9 @@ namespace Avalonia.Win32
WM_TRAYMOUSE = WindowsMessage.WM_USER + 1024,
}
private class TrayIconMenuFlyoutPresenter : MenuFlyoutPresenter, IStyleable
private class TrayIconMenuFlyoutPresenter : MenuFlyoutPresenter
{
Type IStyleable.StyleKey => typeof(MenuFlyoutPresenter);
protected override Type StyleKeyOverride => typeof(MenuFlyoutPresenter);
public override void Close()
{

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

@ -1031,9 +1031,9 @@ namespace Avalonia.Controls.UnitTests
textShaperImpl: new HeadlessTextShaperStub()));
}
private class ItemsControlWithContainer : ItemsControl, IStyleable
private class ItemsControlWithContainer : ItemsControl
{
Type IStyleable.StyleKey => typeof(ItemsControl);
protected override Type StyleKeyOverride => typeof(ItemsControl);
protected internal override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
@ -1046,9 +1046,9 @@ namespace Avalonia.Controls.UnitTests
}
}
private class ContainerControl : ContentControl, IStyleable
private class ContainerControl : ContentControl
{
Type IStyleable.StyleKey => typeof(ContentControl);
protected override Type StyleKeyOverride => typeof(ContentControl);
}
private record Item(string Caption, string? Value = null);

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

@ -1392,9 +1392,9 @@ namespace Avalonia.Controls.UnitTests.Primitives
public void Toggle(int index) => UpdateSelection(index, true, false, true);
}
private class TestSelectorWithContainers : TestSelector, IStyleable
private class TestSelectorWithContainers : TestSelector
{
Type IStyleable.StyleKey => typeof(TestSelector);
protected override Type StyleKeyOverride => typeof(TestSelector);
protected internal override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{

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

@ -603,9 +603,9 @@ namespace Avalonia.Controls.UnitTests
public string Value { get; }
}
private class TestTabControl : TabControl, IStyleable
private class TestTabControl : TabControl
{
Type IStyleable.StyleKey => typeof(TabControl);
protected override Type StyleKeyOverride => typeof(TabControl);
public new ISelectionModel Selection => base.Selection;
}
}

56
tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

@ -1,12 +1,10 @@
using System;
using Moq;
using Avalonia.Collections;
using System.ComponentModel;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Styling;
using Moq;
using Xunit;
using System.ComponentModel;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using System.Collections.Generic;
namespace Avalonia.Markup.Xaml.UnitTests.Converters
{
@ -112,61 +110,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
return tdMock.Object;
}
private class Class1 : AvaloniaObject, IStyleable
private class Class1 : StyledElement
{
public static readonly StyledProperty<string> FooProperty =
AvaloniaProperty.Register<Class1, string>("Foo");
public IAvaloniaReadOnlyList<string> Classes
{
get { throw new NotImplementedException(); }
}
public string Name
{
get { throw new NotImplementedException(); }
}
public Type StyleKey
{
get { throw new NotImplementedException(); }
}
public AvaloniaObject TemplatedParent
{
get { throw new NotImplementedException(); }
}
public ControlTheme GetEffectiveTheme()
{
throw new NotImplementedException();
}
public ThemeVariant ThemeVariant
{
get { throw new NotImplementedException(); }
}
public event EventHandler ThemeVariantChanged;
public void DetachStyles()
{
throw new NotImplementedException();
}
public void DetachStyles(IReadOnlyList<IStyle> styles)
{
throw new NotImplementedException();
}
public void InvalidateStyles()
{
throw new NotImplementedException();
}
public void StyleApplied(IStyleInstance instance)
{
throw new NotImplementedException();
}
public event EventHandler ThemeVariantChanged;
}
private class AttachedOwner

Loading…
Cancel
Save