Monthly Archives: March 2012

Creating a Notification button in WPF

Recently, I had to create a button that besides doing it’s normal job, would also show the amount of tasks that the User had to do inside that button.

To do this I decided to mimic the Notification button of popular Mobile Operating Systems do these days

ios_notifywp_notify

The end result was this:

notify

The full download of the solution can be found here

1.Button Framework

The button inherits from the WPF base button class, and is declared in a custom user control library, so the button style, that is not looked at in this post, is declared in the Generic.xaml file.

It implements a set of dependency properties to control the pooling for new Notifications, and the actual red circle showing the number is a WPF Adorner, so that it can overlap content on the visual tree and abstract the developer from the implications on the layout while using it.

To keep a clean WPF structure I implemented a fake delegate on a ViewModel that mimics our functionality of pooling a Web Service, this ViewModel is attached to the MainWindow, but in the real world we would have something like a menu bar and a menu bar ViewModel and these things would be implemented in those places.

The button usage is as follow:

<StackPanel VerticalAlignment="Center" Orientation="Vertical">
    <lib:NotificationButton Width="40"
                            Height="40"
                            NotifyFunc="{Binding NotifyDelegate}"
                            Poll="{Binding ElementName=PollCheck,
                                            Path=IsChecked}"
                            PollInterval="5">

        <TextBlock HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    FontSize="20"
                    FontWeight="Bold">
            R
        </TextBlock>

    </lib:NotificationButton>

    <CheckBox Name="PollCheck" IsChecked="True" />
</StackPanel>

2. Dependency Properties

The button exposes the following functionality:

  • NotifyFunc – The delegate of type Func<int> that gives you the Notification Number
  • Poll – True or False, tells the button whether to poll or not. Useful for scenarios where for example the button is hidden or collapsed and you want to stop polling.
  • PollInterval – In seconds, allows to configure the interval of the polling.

The implementation looks something like this:

#region Dependency Properties
#region PollInterval
public static readonly DependencyProperty PollIntervalProperty = DependencyProperty.Register(
    "PollInterval",
    typeof(Int32),
    typeof(NotificationButton),
    new UIPropertyMetadata(60) // default value for this DP
    );

public int PollInterval
{
    get { return (int)GetValue(PollIntervalProperty); }
    set { SetValue(PollIntervalProperty, value); }
}
#endregion

#region Poll
public static readonly DependencyProperty PollingProperty = DependencyProperty.Register(
    "Poll",
    typeof(Boolean),
    typeof(NotificationButton),
    new UIPropertyMetadata(true, OnPollChanged) // default value for this DP
    );

public bool Poll
{
    get { return (bool)GetValue(PollingProperty); }
    set { SetValue(PollingProperty, value); }
}

public void StartPolling()
{
    _timer.Change(PollInterval * 1000, PollInterval * 1000);
}

public void StopPolling()
{
    _timer.Change(Timeout.Infinite, Timeout.Infinite);
}

private static void OnPollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var nb = (NotificationButton)d;

    if ((bool)e.NewValue) nb.StartPolling();
    else nb.StopPolling();
}
#endregion

#region NotificationFunc
public static readonly DependencyProperty NotifyFuncProperty = DependencyProperty.Register(
    "NotifyFunc",
    typeof(Func<int>),
    typeof(NotificationButton)
    );

public Func<int> NotifyFunc
{
    get { return (Func<int>)GetValue(NotifyFuncProperty); }
    set { SetValue(NotifyFuncProperty, value); }
}
#endregion
#endregion

3.The Adorner

The Adorner is implemented on the same file as the button, because it is not shared by other controls, however you could isolate it on a different file to make it easier to reuse it.

The only specific thing is that I use the drawing context to draw direct primitives instead of instantiating WPF controls:

/// <summary>
/// The Notification adorner.
/// On Render implements direct drawing context calls.
/// </summary>
public class NotificationAdorder : Adorner
{
    public NotificationAdorder(UIElement adornedElement)
        : base(adornedElement)
    {
        DataContextChanged += NotificationAdorderDataContextChanged;
    }

    private void NotificationAdorderDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        InvalidateVisual();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (DataContext == null) return; // Break if we don't have the Adorner databound

        var adornedElementRect = new Rect(AdornedElement.DesiredSize);
        var typeFace = new Typeface(new FontFamily("Courier New"), FontStyles.Normal, FontWeights.ExtraBold,
                                    FontStretches.Condensed);

        var text = new FormattedText(
            DataContext.ToString(),
            CultureInfo.GetCultureInfo("en-us"),
            FlowDirection.RightToLeft,
            typeFace,
            16, Brushes.White
            );

        var pointOfOrigin = new Point
                                {
                                    X = adornedElementRect.BottomRight.X,
                                    Y = adornedElementRect.BottomRight.Y - text.Height * 0.7
                                };

        var elipseCenter = new Point
                                {
                                    X = pointOfOrigin.X - text.Width / 2,
                                    Y = pointOfOrigin.Y + text.Height / 2
                                };

        var elipseBrush = new SolidColorBrush
                                {
                                    Color = Colors.DarkRed,
                                    Opacity = 0.8
                                };

        drawingContext.DrawEllipse(
            elipseBrush,
            new Pen(Brushes.White, 2),
            elipseCenter,
            text.Width * 0.9,
            text.Height * 0.5
            );

        drawingContext.DrawText(text, pointOfOrigin);
    }
}

The adorner is attached to the button on the Loaded delegate

var adornerLayler = AdornerLayer.GetAdornerLayer(this);
_adorner = new NotificationAdorder(this) {DataContext = null};
adornerLayler.Add(_adorner);

Of note is also the fact that we need to capture the datacontext changed even, so that we can call the InvalidateVisual to force WPF to redraw the Adorner.

4.Other Parts of interest

It is important to never block the WPF UI thread of calls to Web Services, to accomplish this I used a bit of TPL code to first get some dependency properties in the UI thread, then call the Web Service in a separate thread, and finally rebind the data in the UI thread:

/// <summary>
/// Delegates the fake Web Service call onto a different thread, while retaining the UI Thread to update the UI
/// </summary>
/// <param name="state"></param>
private void CheckContent(object state = null)
{
    int? delegateReturn = 0;
    Func<int> func = null;

    Task.Factory.StartNew(() =>
                                {
                                    func = NotifyFunc;
                                }
        , new CancellationToken(), TaskCreationOptions.None, _uiContext)
        .ContinueWith(t =>
                            {
                                delegateReturn = func();
                                if (delegateReturn < 1) delegateReturn = null; // Normalize to not show if not at least 1
                            }
        )
        .ContinueWith(t =>
                        {
                            _adorner.DataContext = delegateReturn;
                        }
        , new CancellationToken(), TaskContinuationOptions.None, _uiContext);
}

To control the pooling I used a System.Threading.Timer that is created on the Loaded delegate:

_timer = new Timer(CheckContent, null, Timeout.Infinite, Timeout.Infinite);
if (Poll) _timer.Change(PollInterval * 1000, PollInterval * 1000);

And controlled by these 2 utility methods:

public void StartPolling()
{
    _timer.Change(PollInterval * 1000, PollInterval * 1000);
}

public void StopPolling()
{
    _timer.Change(Timeout.Infinite, Timeout.Infinite);
}

5.The ViewModel

It is a very simple ViewModel, that basically declared and implements the Function delegate:

/// <summary>
/// The view model, with a fake implementation that calls a 1 sec sleep to make sure the UI is never blocked.
/// </summary>
public class NotificationViewModel
{
    public NotificationViewModel()
    {
        NotifyDelegate = NotificationImplementation;
    }

    public Func<int> NotifyDelegate { get; set; }

    private static int NotificationImplementation()
    {
        var r = new Random();
        Thread.Sleep(1000);
        return r.Next(1, 99);
    }
}

Managing an Application’s ADLDS through Powershell

Sometimes, an application requires an Authentication provider that both uses an Enterprise’s Active Directory and at the same time stores application scope accounts for external users. Microsoft recommends using Active Directory Lightweight Directory Services, or ADLDS, to accomplish this.

We have a scenario where we have a WPF application that is authenticating in an ADLDS through a WCF Web Service, this application has a specific set of groups, and requires the ADLDS to be properly configured.

To accomplish these tasks, yesterday I had to create 2 powershell scrips, one to setup the entire ADLDS with the default groups and a default user per group for the test team to use during testing, and a second one to add a single user to a specific group.

1. Setting up our entire ADLDS

This script creates a set of defined application groups, and populates each one of them with a test user. It also creates a general admin user. I’m using the ` line escape to make the script readable in the blog.

Import-Module ActiveDirectory

##############################################################################################
# Array with groups for AD LDS
$ADGroups = @("GROUP1", "GROUP2", "GROUP3",
              "GROUP4", "GROUP5", "GROUP6",
              "GROUP7", "GROUP8", "GROUP9",
              "GROUP10", "GROUP11", "GROUP12")
##############################################################################################

##############################################################################################
# Username, Password of an admin account for the AD LDS and the location of the AD LDS
$credUsername = 'MyDomain\MyUser'
$credPassword = 'MyPassword'
$server = 'Myserver'
##############################################################################################

$cred = New-Object System.Management.Automation.PSCredential -ArgumentList `
        @($credUsername,(ConvertTo-SecureString -String $credPassword -AsPlainText -Force))

Write-Host
Write-Host "Creating User Groups ..."

foreach ($element in $ADGroups)
{
    New-ADGroup -Name $element -SamAccountName $element -DisplayName $element `
        -GroupCategory Distribution -GroupScope DomainLocal -Path "CN=Roles,CN=MyCN,DC=PT" `
        -OtherAttributes @{isCriticalSystemObject='TRUE'} -Server $server -Credential $cred
}

Write-Host "Creating the user my_admin ..."

$userName = "my_admin"
New-ADUser -Name $userName -SamAccountName $userName -DisplayName $userName `
    -Path "CN=Users,CN=MyCN,DC=PT" `
    -AccountPassword (ConvertTo-SecureString -AsPlainText "MyPassword" -Force) -Enabled $true `
    -PasswordNeverExpires $true -Server $server -Credential $cred

$user = Get-ADUser -Filter {cn -eq $userName} -SearchBase "CN=Users,CN=MyCN,DC=PT" `
    -server $server -Credential $cred 

Get-ADGroup -Filter {cn -eq "ADMINISTRADOR"} -SearchBase "CN=Roles,CN=MyCN,DC=PT" `
    -server $server -Credential $cred
    | Add-ADGroupMember -Members $user -Server $server -Credential $cred

Write-Host "Creating test users ..."

foreach ($element in $ADGroups)
{
    $userName = "mytest_" + $element.ToLower()
    New-ADUser -Name $userName -SamAccountName $userName -DisplayName $userName `
        -Path "CN=Users,CN=MyCN,DC=PT" `
        -AccountPassword (ConvertTo-SecureString -AsPlainText "testuserpwd" -Force) `
        -Enabled $true -PasswordNeverExpires $true -Server $server -Credential $cred

    $user = Get-ADUser -Filter {cn -eq $userName} -SearchBase "CN=Users,CN=MyCN,DC=PT" `
        -server $server -Credential $cred

    Get-ADGroup -Filter {cn -eq $element} -SearchBase "CN=Roles,CN=MyCN,DC=PT" `
        -server $server -Credential $cred
        | Add-ADGroupMember -Members $user -Server $server -Credential $cred
}

Write-Host
Write-Host "ADLDS sucessfuly configured" -foregroundcolor green

2. Script to create a specific ADLDS user and assign it to a group

This scrip prompts for the user group, the user name and the user password, creates the user and assigns it to the group. I’m using the ` line escape to make the script readable in the blog.

Import-Module ActiveDirectory

##############################################################################################
# Array with groups for AD LDS
$ADGroups = @("GROUP1", "GROUP2", "GROUP3",
              "GROUP4", "GROUP5", "GROUP6",
              "GROUP7", "GROUP8", "GROUP9",
              "GROUP10", "GROUP11", "GROUP12")
##############################################################################################

##############################################################################################
# Username, Password of an admin account for the AD LDS and the location of the AD LDS
$credUsername = 'MyDomain\MyUser'
$credPassword = 'MyPassword'
$server = 'Myserver'
##############################################################################################

$cred = New-Object System.Management.Automation.PSCredential -ArgumentList `
        @($credUsername,(ConvertTo-SecureString -String $credPassword -AsPlainText -Force))

Clear-Host
Write-Host "=================";
Write-Host "= Chose a group =";
Write-Host "=================";

[char]$startNumber = 'A'
[char]$lastNumber = 'L'
[char]$groupNumber = $startNumber

foreach ($element in $ADGroups)
{
    Write-Host $groupNumber : $element
    $groupNumber = [char]([int]$groupNumber +1)
}

$input = Read-Host "Enter the Group Letter"

$groupPosition = ([int]($input.ToUpper().ToCharArray())[0]) - [int]$startNumber
$ADGroup = $ADGroups[$groupPosition]

Write-Host
Write-Host "Creating user in group", $ADGroups[$groupPosition]

$userName = Read-Host "User Name (without spaces)"
$userPassword = Read-Host "User Password"

New-ADUser -Name $userName -SamAccountName $userName -DisplayName $userName `
    -Path "CN=Users,CN=MyCN,DC=PT" `
    -AccountPassword (ConvertTo-SecureString -AsPlainText $userPassword -Force) -Enabled $true `
    -PasswordNeverExpires $true -Server $server -Credential $cred

$user = Get-ADUser -Filter {cn -eq $userName} -SearchBase "CN=Users,CN=MyCN,DC=PT" `
    -server $server -Credential $cred 

Get-ADGroup -Filter {cn -eq $ADGroup} -SearchBase "CN=Roles,CN=MyCN,DC=PT" `
    -server $server -Credential $cred
    | Add-ADGroupMember -Members $user -Server $server -Credential $cred

Write-Host
Write-Host "User created sucessfully" -foregroundcolor green