Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Custom & User Controls Sample #1786

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions WinUIGallery/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
<ResourceDictionary Source="/Styles/Grid.xaml" />
<ResourceDictionary Source="/Styles/GridViewItem.xaml" />
<ResourceDictionary Source="/Styles/TextBlock.xaml" />
<ResourceDictionary Source="/Samples/ControlPages/Fundamentals/Controls/CounterControl.xaml" />
<ResourceDictionary Source="/Samples/ControlPages/Fundamentals/Controls/ValidatedPasswordBox.xaml" />
</ResourceDictionary.MergedDictionaries>

<ResourceDictionary.ThemeDictionaries>
Expand Down
5 changes: 5 additions & 0 deletions WinUIGallery/Pages/NavigationRootPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@
AutomationProperties.AutomationId="Templates"
Content="Templates"
Tag="Templates" />
<NavigationViewItem
x:Name="CustomUserControlsPage"
AutomationProperties.AutomationId="CustomUserControls"
Content="Custom &amp; User Controls"
Tag="CustomUserControls" />
<NavigationViewItem
x:Name="ScratchPadPage"
AutomationProperties.AutomationId="ScratchPad"
Expand Down
4 changes: 4 additions & 0 deletions WinUIGallery/Pages/NavigationRootPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ private void OnNavigationViewSelectionChanged(NavigationView sender, NavigationV
{
Navigate(typeof(ItemPage), "Templates");
}
else if (selectedItem == CustomUserControlsPage)
{
Navigate(typeof(ItemPage), "CustomUserControls");
}
else if (selectedItem == ScratchPadPage)
{
Navigate(typeof(ItemPage), "ScratchPad");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace WinUIGallery.Samples.ControlPages.Fundamentals.Controls;

public enum CounterMode
{
Increment,
Decrement
}

public sealed class CounterControl : Control
{
public static readonly DependencyProperty CountProperty =
DependencyProperty.Register(nameof(Count), typeof(int), typeof(CounterControl), new PropertyMetadata(0));

public static readonly DependencyProperty ModeProperty =
DependencyProperty.Register(nameof(Mode), typeof(CounterMode), typeof(CounterControl), new PropertyMetadata(CounterMode.Increment));

public CounterControl()
{
this.DefaultStyleKey = typeof(CounterControl);
}

public int Count
{
get => (int)GetValue(CountProperty);
set => SetValue(CountProperty, value);
}

public CounterMode Mode
{
get => (CounterMode)GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}

private Button ActionButton;
private TextBlock CountText;

protected override void OnApplyTemplate()
{
base.OnApplyTemplate();

ActionButton = GetTemplateChild(nameof(ActionButton)) as Button;
CountText = GetTemplateChild(nameof(CountText)) as TextBlock;

if (ActionButton is not null)
{
ActionButton.Click += (sender, e) =>
{
Count = Mode == CounterMode.Increment ? Count + 1 : Count - 1;
UpdateUI();
};

UpdateButtonText();
}

UpdateUI();
}

private void UpdateUI()
{
if (CountText is not null)
{
CountText.Text = Count.ToString();
}
}

private void UpdateButtonText()
{
if (ActionButton is not null)
{
ActionButton.Content = Mode == CounterMode.Increment ? "Increase" : "Decrease";
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinUIGallery.Samples.ControlPages.Fundamentals.Controls">

<!-- Style definition for CounterControl -->
<Style TargetType="local:CounterControl">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe as a best practice, we could name this style and create a seperate style that does the overriding. That way a developer could use BasedOn

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this might be the best practice, but would it be simple for someone who just wants to learn the fundamentals?
I think the code is good as it is, especially since this is the approach used when creating a new custom control.
However, if you believe this should be included in the example, I'd be happy to implement it 😊

<Setter Property="Template">
<Setter.Value>
<!-- ControlTemplate defines the structure of CounterControl -->
<ControlTemplate TargetType="local:CounterControl">
<StackPanel HorizontalAlignment="Left"
Spacing="8">
<TextBlock x:Name="CountText"
FontSize="20"
Text="0"
HorizontalAlignment="Center" />
<Button x:Name="ActionButton"
Content="Increase"
HorizontalAlignment="Center"
Width="100" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System.Linq;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace WinUIGallery.Samples.ControlPages.Fundamentals.Controls;

public sealed class ValidatedPasswordBox : Control
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register(nameof(Password), typeof(string), typeof(ValidatedPasswordBox), new PropertyMetadata(string.Empty, OnPasswordChanged));

public static readonly DependencyProperty IsValidProperty =
DependencyProperty.Register(nameof(IsValid), typeof(bool), typeof(ValidatedPasswordBox), new PropertyMetadata(false));

public static readonly DependencyProperty MinLengthProperty =
DependencyProperty.Register(nameof(MinLength), typeof(int), typeof(ValidatedPasswordBox), new PropertyMetadata(8, OnPasswordChanged));

public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register(nameof(Header), typeof(string), typeof(ValidatedPasswordBox), new PropertyMetadata(string.Empty, OnPasswordChanged));

public static readonly DependencyProperty PlaceholderTextProperty =
DependencyProperty.Register(nameof(PlaceholderText), typeof(string), typeof(ValidatedPasswordBox), new PropertyMetadata(string.Empty, OnPasswordChanged));

public ValidatedPasswordBox()
{
this.DefaultStyleKey = typeof(ValidatedPasswordBox);
}

public string Password
{
get => (string)GetValue(PasswordProperty);
set => SetValue(PasswordProperty, value);
}

public bool IsValid
{
get => (bool)GetValue(IsValidProperty);
set => SetValue(IsValidProperty, value);
}

public int MinLength
{
get => (int)GetValue(MinLengthProperty);
set => SetValue(MinLengthProperty, value);
}

public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}

public string PlaceholderText
{
get => (string)GetValue(PlaceholderTextProperty);
set => SetValue(PlaceholderTextProperty, value);
}

private PasswordBox PasswordInput { get; set; }
private StackPanel MissingUppercaseText { get; set; }
private StackPanel MissingNumberText { get; set; }
private StackPanel TooShortText { get; set; }
private StackPanel ValidPasswordText { get; set; }

protected override void OnApplyTemplate()
{
base.OnApplyTemplate();

PasswordInput = GetTemplateChild(nameof(PasswordInput)) as PasswordBox;
if (PasswordInput != null)
{
PasswordInput.Header = Header;
PasswordInput.PlaceholderText = PlaceholderText;
}

MissingUppercaseText = GetTemplateChild(nameof(MissingUppercaseText)) as StackPanel;
MissingNumberText = GetTemplateChild(nameof(MissingNumberText)) as StackPanel;
TooShortText = GetTemplateChild(nameof(TooShortText)) as StackPanel;
ValidPasswordText = GetTemplateChild(nameof(ValidPasswordText)) as StackPanel;

if (PasswordInput is not null)
{
PasswordInput.PasswordChanged += (sender, e) =>
{
Password = PasswordInput.Password;
UpdateValidationMessages();
};
}

UpdateValidationMessages();
}

private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (ValidatedPasswordBox)d;
control.UpdateValidationMessages();
}

private void UpdateValidationMessages()
{
bool hasMinLength = Password.Length >= MinLength;
bool hasUppercase = Password.Any(char.IsUpper);
bool hasNumber = Password.Any(char.IsDigit);

IsValid = hasMinLength && hasUppercase && hasNumber;

if (MissingUppercaseText is not null)
{
MissingUppercaseText.Visibility = (hasUppercase || string.IsNullOrEmpty(Password)) ? Visibility.Collapsed : Visibility.Visible;
}

if (MissingNumberText is not null)
{
MissingNumberText.Visibility = (hasNumber || string.IsNullOrEmpty(Password)) ? Visibility.Collapsed : Visibility.Visible;
}

if (TooShortText is not null)
{
TooShortText.Visibility = (hasMinLength || string.IsNullOrEmpty(Password)) ? Visibility.Collapsed : Visibility.Visible;
}

if (ValidPasswordText is not null)
{
ValidPasswordText.Visibility = IsValid ? Visibility.Visible : Visibility.Collapsed;
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WinUIGallery.Samples.ControlPages.Fundamentals.Controls">

<Style TargetType="local:ValidatedPasswordBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ValidatedPasswordBox">
<StackPanel Spacing="4">
<PasswordBox x:Name="PasswordInput" />

<!-- Uppercase Validation -->
<StackPanel x:Name="MissingUppercaseText"
Orientation="Horizontal">
<FontIcon Glyph="&#xEA39;"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
FontSize="14"
Margin="0,0,4,0" />
<TextBlock Text="Missing uppercase"
Foreground="{ThemeResource SystemFillColorCriticalBrush}" />
</StackPanel>

<!-- Number Validation -->
<StackPanel x:Name="MissingNumberText"
Orientation="Horizontal">
<FontIcon Glyph="&#xEA39;"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
FontSize="14"
Margin="0,0,4,0" />
<TextBlock Text="Missing number"
Foreground="{ThemeResource SystemFillColorCriticalBrush}" />
</StackPanel>

<!-- Length Validation -->
<StackPanel x:Name="TooShortText"
Orientation="Horizontal">
<FontIcon Glyph="&#xEA39;"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
FontSize="14"
Margin="0,0,4,0" />
<TextBlock Text="Too short!"
Foreground="{ThemeResource SystemFillColorCriticalBrush}" />
</StackPanel>

<!-- Valid Password -->
<StackPanel x:Name="ValidPasswordText"
Orientation="Horizontal">
<FontIcon Glyph="&#xE930;"
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
FontSize="14"
Margin="0,0,4,0" />
<TextBlock Text="Password is valid"
Foreground="{ThemeResource SystemFillColorSuccessBrush}" />
</StackPanel>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Loading