Describe the bug
Border properties like BorderBrush or BorderThickness can't change from xaml.
Steps to reproduce the bug
Steps to reproduce the behavior:
<ComboBox Header="ComboxBorder" BorderThickness="0.5" BorderBrush="DarkGray" />
<controls:NumberBox Header="ComboxBorder" BorderThickness="0.5" BorderBrush="DarkGray"/>
Expected behavior
Border properties should work like other controls (ComboBox, TextBox,...)
Screenshots
Version Info
NuGet package version:
| Windows 10 version | Saw the problem? |
| :--------------------------------- | :-------------------- |
| Insider Build (xxxxx) | |
| November 2019 Update (18363) | Yes |
| May 2019 Update (18362) | |
| October 2018 Update (17763) | |
| April 2018 Update (17134) | |
| Fall Creators Update (16299) | |
| Creators Update (15063) | |
| Device form factor | Saw the problem? |
| :-------------------- | :------------------- |
| Desktop | Yes |
| Mobile | |
| Xbox | |
| Surface Hub | |
| IoT | |
Additional context
@SavoySchuler @teaP Should we expose these TextBox properties onto NumberBox through a template bind ? Are there any other TextBox properties that would fall under the same category ?
@ranjeshj - thanks for the chat, that's exactly what we should do. @teaP is there anything you need from my end to expose these TextBox properties onto NumberBox through a template bind?
@teaP Are you currently working on this (or soon) or is this issue up for grabs for the community?
@Felix-Dev, it is up for grabs now :)
In that case, I would like to tackle this one.
To clarify: In SpinButtonPlacementMode.Inline, if we apply properties like BorderThickness and BorderBrush, is the desired look the following?
<controls:NumberBox
BorderBrush="Orange"
BorderThickness="4"/>
To clarify: In SpinButtonPlacementMode.Inline, if we apply properties like BorderThickness and BorderBrush, is the desired look the following?
I would say so yes
To clarify: In SpinButtonPlacementMode.Inline, if we apply properties like BorderThickness and BorderBrush, is the desired look the following?
<controls:NumberBox BorderBrush="Orange" BorderThickness="4"/>
I can't image if the BorderThickness=0, what will spin button looks like?
@hipo760
Setting the BorderThickness to 0 for the NumberBox would result in the following look:
and mouse hovering over the increase button:
@hipo760
Setting the BorderThickness to 0 for the NumberBox would result in the following look:
and mouse hovering over the increase button:
Looks so great, thank you!!
@ranjeshj Currently, we expose the border thickness of the NumberBox Inline SpinButtons as a resource:
<Thickness x:Key="NumberBoxSpinButtonBorderThickness">0,1,1,1</Thickness>
Note that we don't set the left border side here. This way, the border between the two spin buttons has the same thickness as the other border sides (e.g. top, bottom) as can be seen in the images above.
In order to make the SpinButtons border settable via a BorderThickness TemplateBinding (so that NumberBox.BorderThickness affects the whole NumberBox and not just its internal TextBox) I could add a new API similar to the CornerRadiusFilter API. The API would enable us to specify a mask to apply to a Thickness
value in order to ignore the thickness value for specific sides. With the CornerRadiusFilter API we specify which corner radius values to keep using the Filter
property. The Thickness masking API would operate the other way: Specifying the border side we want to ignore (set to 0).
An example use case for the NumberBox could look like this:
<!-- The Thickness masking resource to use -->
<primitives:ThicknessMaskConverter x:Key="LeftThicknessMaskConverter" Mask="Left"/>
<!-- Use the above Thickness masking resource for NumberBox' SpinButtons in Inline mode -->
<RepeatButton x:Name="DownSpinButton"
BorderThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness, Converter={StaticResource LeftThicknessMaskConverter}}"
Style="{StaticResource NumberBoxSpinButtonStyle}"/>
Thoughts?
@Felix-Dev That sounds like a reasonable approach. This would be new API though, so adding @SavoySchuler and @MikeHillberg.
Just assigned myself so that I can take a look at this.
@SavoySchuler I have finalized my API proposal now (including a test implementation in a local branch of mine).
To recap, the NumberBox uses three borders to achieve its visual look in all the different visual states: A border around its input field (the TextBox and its border) and around each of its SpinButtons when in SpinButtonPlacementMode::Inline. See the following image of a NumberBox in rest mode:
To achieve the uniform border thickness used here, the border thickness values of the spin buttons are of the form 0,X,X,X
(so the border's left side thickness is set to 0).
Currently, setting the NumberBox.BorderBrush
API has no effect on the NumberBox which @hipo760 lists in their opening post. If one wishes to easily customize the border thickness of aNumberBox, lightweight styling has to be used instead for now. To add support for the NumberBox.BorderThickness
API and make it work as intended, we need to filter the template bound BorderThickness for the spin buttons accordingly. In other words, we need to set the Thickness.Left
field of a given Thickness
value to 0 (zero). See the following GIF which shows what will happen to the NumberBox's border if we template bind to the NumberBox.BorderThickness
without filtering the thickness value as described above:
<RepeatButton x:Name="UpSpinButton"
BorderThickness="{TemplateBinding BorderThickness}"/>
As a comparison, here is how the NumberBox.BorderThickness
API will be applied with correct filtering of the template bound BorderThickness:
To achieve this, I propose adding a new API similar to the existing CornerRadiusFilterConverter API. This new API will be used to create a new Thickness
struct from an existing one, while using a configurable mask to set some of the Thickness
fields to 0 while keeping the rest. See below for an API proposal and an API example.
[flags]
enum ThicknessMaskKinds
{
None = 0,
Top = 1,
Right = 2,
Bottom = 4,
Left = 8
};
runtimeclass ThicknessMaskConverter : Windows.UI.Xaml.DependencyObject, Windows.UI.Xaml.Data.IValueConverter
{
ThicknessMaskConverter();
[MUX_DEFAULT_VALUE("winrt::ThicknessMaskKinds::None")]
ThicknessMaskKinds Mask{ get; set; };
winrt::IInspectable ThicknessMaskConverter::Convert(
winrt::IInspectable const& value,
winrt::TypeName const& targetType,
winrt::IInspectable const& parameter,
winrt::hstring const& language);
winrt::IInspectable ThicknessMaskConverter::ConvertBack(
winrt::IInspectable const& value,
winrt::TypeName const& targetType,
winrt::IInspectable const& parameter,
winrt::hstring const& language);
static Windows.UI.Xaml.DependencyProperty MaskProperty{ get; };
};
| Property | Description |
|:----------|:--------------|
| Mask | Gets or sets the mask applied to the ThicknessMaskConverter
specifying which values of a Thickness
struct will be set to 0 (zero) and which to keep. Can be a combination of multiple ThicknessMaskKinds
members. |
| MaskProperty | Identifies the Mask
dependency property. |
| Method| Description |
|:----------|:--------------|
| Convert(winrt::IInspectable, winrt::TypeName, winrt::IInspectable, winrt::hstring) | Converts the source Thickness
to a new Thickness
by setting some fields to 0 specified by Mask
and leave the rest intact. |
| ConvertBack(winrt::IInspectable, winrt::TypeName, winrt::IInspectable, winrt::hstring) | Not implemented. |
Consuming the proposed API is similar to how you would use the CornerRadiusFilterConverter
API. See, for example, the following XAML below. Here, we first create a new instance of the ThicknessMaskConverter
class with our desired Mask
and then use our converter in a binding. For demo purposes, I created an example Thickness
resource called ExampleThickness in order to use a binding. In the NumberBox ControlTemplate case, we would instead work with a commonly used TemplatedParent relative binding.
<Page
xmlns:primitives="using:Microsoft.UI.Xaml.Controls.Primitives">
<Page.Resources>
<primitives:ThicknessMaskConverter x:Key="LeftTopRightThicknessMaskConverter" Mask="Left,Top,Right"/>
<Thickness x:Key="ExampleThickness">4</Thickness>
</Page.Resources>
<Grid x:Name="RootGrid">
<TextBox
Text="I am a TextBox with just a bottom border"
BorderBrush="GreenYellow"
BorderThickness="{Binding Source={StaticResource ExampleThickness}, Converter={StaticResource LeftTopRightThicknessMaskConverter}}" />
</Grid>
</Page>
The XAML above gives us the following TextBox look:
As already mentioned above we would use the ThicknessMaskConverter
API in the NumberBox ControlTemplate like this:
<RepeatButton x:Name="UpSpinButton"
BorderThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness, Converter={StaticResource LeftThicknessMaskConverter}}"
Style="{StaticResource NumberBoxSpinButtonStyle}"/>
where LeftThicknessMaskConverter is defined as:
<primitives:ThicknessMaskConverter x:Key="LeftThicknessMaskConverter" Mask="Left"/>
First and foremost, I'd like to give a shoutout to @stevenbrix who is always a great help when I have specific implementation questions. Thank you, Steve!
Secondly, while we can view this API as filtering out specific Thickness
fields and throwing away others (by setting them to 0) I went with the "Mask" terminology for now. The reason being that I can imagine we might be more interested interested in ignoring the value of only 1-2 fields of a Thickness
instead of 3-4 fields. Take our NumberBox case here, for example. We filter out the Top, Right and Bottom values of a Thickness
value and ignore only the Left
value. If I would have gone with the filter terminology, here's how my XAML would have looked like instead:
<primitives:ThicknessFilterConverter x:Key="TopRightBottomThicknessFilterConverter" Filter="Top,Right,Bottom"/>
<RepeatButton x:Name="UpSpinButton"
BorderThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness, Converter={StaticResource TopRightBottomThicknessFilterConverter}}"
Style="{StaticResource NumberBoxSpinButtonStyle}"/>
Compare that to the XAML using the Mask
terminology:
<primitives:ThicknessMaskConverter x:Key="LeftThicknessMaskConverter" Mask="Left"/>
Obviously, I am open to renaming and restructuring the proposed API if the team prefers the name ThicknessFilterConverter (or any other name) over my proposed naming.
The proposed ThicknessMaskConverter
API will be a great helper API to achieve proper NumberBox.BorderThickness
support. More broadly speaking it is to be used in a (Template) Binding to create a new Thickness
struct from an existing one, setting only some of the fields to 0 while keeping the rest.
@SavoySchuler Any update here?
Most helpful comment
To clarify: In SpinButtonPlacementMode.Inline, if we apply properties like BorderThickness and BorderBrush, is the desired look the following?