Saturday 23 November 2013

PropertyListener

Need to react to a property changing but the control doesn’t expose the event?  No problem, you can use the PropertyListener:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Utilities
{
public class PropertyListener
{
/// <summary>
/// Listens for changes to dependency properties on a given FrameworkElement calls a callback on property change.
/// Usage example: PropertyListener.ListenForChange("IsBusy", BusyIndicator, (d,e) => DoStuff())
/// (may cause memory leaks)
/// </summary>
public void ListenForChange(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var b = new Binding(propertyName) { Source = element };
var prop = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(Control),
new PropertyMetadata(callback));
element.SetBinding(prop, b);
}
}
}

RichText in a TextBlock

This project contains examples for Silverlight for providing formatting within a text block, either as a behaviour or attached property.  I would have liked to create custom control that derived from TextBlock but the class is sealed in Silverlight.

Source code:  https://github.com/stevenh77/RichTextBlockExample

image

<UserControl x:Class="RichTextBlockExample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:RichTextBlockExample"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<TextBlock x:Name="TextBlockUsingBehaviour">
<i:Interaction.Behaviors>
<local:RichTextBlockBehaviour/>
</i:Interaction.Behaviors>
</TextBlock>

<TextBlock x:Name="TextBlockUsingAttachedProperty" local:SupportRichText.RichText="" />
</StackPanel>
</Grid>
</UserControl>

namespace RichTextBlockExample
{
public partial class MainPage
{
public MainPage()
{
InitializeComponent();

string output = "Testing <bold>formatted</bold> text <underline>with</underline> a <italic>textblock</italic>";
TextBlockUsingBehaviour.Text = output;
TextBlockUsingAttachedProperty.SetValue(SupportRichText.RichTextProperty, output);
}
}
}

Behaviour


using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Interactivity;

namespace RichTextBlockExample
{
public class RichTextBlockBehaviour : Behavior<TextBlock>
{
private PropertyListener propertyListener;
protected override void OnAttached()
{
base.OnAttached();
propertyListener = new PropertyListener();
propertyListener.ListenForChange("Text", this.AssociatedObject, TextBlock_TextChanged);
}

private void TextBlock_TextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textBlock = sender as TextBlock;
if (textBlock != null)
{
textBlock.Inlines.Clear();
textBlock.Inlines.Add(Traverse(e.NewValue.ToString()));
}
}

protected override void OnDetaching()
{
base.OnDetaching();
}

private static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);

// Check for grouping
if (sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.

// Check for token
if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart)
? null
: section.Substring(token.Length, section.Length - 1 - token.Length * 2);

switch (token.ToLower())
{
case "<bold>":
return new Run() { Text = content, FontWeight = FontWeights.Bold };

case "<italic>":
return new Run() { Text = content, FontStyle = FontStyles.Italic };

case "<underline>":
return new Run() { Text = content, TextDecorations = TextDecorations.Underline };

case "<linebreak/>":
return new LineBreak();

default:
return new Run() { Text = content };
}
}
else return new Run() { Text = section };
}
else // Group together
{
Span span = new Span();

foreach (string section in sections) span.Inlines.Add(Traverse(section));

return span;
}
}

/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
private static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;

startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");

// No token here
if (startIndex < 0) return false;

// No token here
if (startTokenEndIndex < 0) return false;

token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);

// Check for closed token. E.g. <LineBreak/>
if (token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}

string endToken = token.Insert(1, "/");

// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);

if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if (temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;

}
while (nesting > 0);

endIndex = pos;

return true;
}

/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
private static string[] SplitIntoSections(string value)
{
List<string> sections = new List<string>();

while (!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;

// Check if this is a token section
if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if (tokenStartIndex > 0) sections.Add(value.Substring(0, tokenStartIndex));

sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{
// No tokens, just add the text
sections.Add(value);
value = null;
}
}

return sections.ToArray();
}
}
}

Attached Property


using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace RichTextBlockExample
{
public class SupportRichText
{
public static string GetRichText(DependencyObject obj)
{
return (string)obj.GetValue(RichTextProperty);
}

public static void SetRichText(DependencyObject obj, string value)
{
obj.SetValue(RichTextProperty, value);
}

public static readonly DependencyProperty RichTextProperty =
DependencyProperty.RegisterAttached(
"RichText",
typeof(string),
typeof(SupportRichText),
new PropertyMetadata("", RichTextChanged));

private static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);

// Check for grouping
if (sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.

// Check for token
if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart)
? null
: section.Substring(token.Length, section.Length - 1 - token.Length * 2);

switch (token.ToLower())
{
case "<bold>":
return new Run() { Text = content, FontWeight = FontWeights.Bold };

case "<italic>":
return new Run() { Text = content, FontStyle = FontStyles.Italic };

case "<underline>":
return new Run() { Text = content, TextDecorations = TextDecorations.Underline };

case "<linebreak/>":
return new LineBreak();

default:
return new Run() { Text = content };
}
}
else return new Run() { Text = section };
}
else // Group together
{
Span span = new Span();

foreach (string section in sections) span.Inlines.Add(Traverse(section));

return span;
}
}

/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
private static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;

startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");

// No token here
if (startIndex < 0) return false;

// No token here
if (startTokenEndIndex < 0) return false;

token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);

// Check for closed token. E.g. <LineBreak/>
if (token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}

string endToken = token.Insert(1, "/");

// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);

if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if (temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;

}
while (nesting > 0);

endIndex = pos;

return true;
}

/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
private static string[] SplitIntoSections(string value)
{
var sections = new List<string>();
while (!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;

// Check if this is a token section
if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if (tokenStartIndex > 0) sections.Add(value.Substring(0, tokenStartIndex));

sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{
// No tokens, just add the text
sections.Add(value);
value = null;
}
}

return sections.ToArray();
}

private static void RichTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
string value = e.NewValue as string;
TextBlock textBlock = sender as TextBlock;
if (textBlock != null) textBlock.Inlines.Add(Traverse(value));
}
}
}

Property Listener


using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace RichTextBlockExample
{
public class PropertyListener
{
/// <summary>
/// Listens for changes to dependency properties on a given FrameworkElement calls a callback on property change.
/// Usage example: PropertyListener.ListenForChange("IsBusy", BusyIndicator, (d,e) => DoStuff())
/// (may cause memory leaks)
/// </summary>
public void ListenForChange(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var b = new Binding(propertyName) { Source = element };
var prop = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(Control),
new PropertyMetadata(callback));
element.SetBinding(prop, b);
}
}
}

Thursday 21 November 2013

DateTimeFormatterConverter for WinRT

With there being no StringFormat on a binding in WinRT yet you can use this converter to leverage the crazy (different) DateTimeFormatter object:

    public class DateTimeFormatterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var formatter = new Windows.Globalization.DateTimeFormatting.DateTimeFormatter(parameter.ToString());
return formatter.Format((DateTime)value);
}

public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

Usage:


<converters1:DateTimeFormatterConverter x:Key="DateTimeFormatterConverter" />

<TextBlock Text="{Binding DealDate,
Converter={StaticResource DateTimeFormatterConverter},
ConverterParameter=day month year}"

Binding visual state manager to a view model

Using an attached property:

using System;
using System.Windows;
using System.Windows.Controls;

namespace StateHelperSL
{
public class VisualStateHelper : DependencyObject
{
public static readonly DependencyProperty VisualStatePropertyProperty = DependencyProperty.RegisterAttached(
"VisualStateProperty",
typeof(string),
typeof(VisualStateHelper),
new PropertyMetadata(VisualStatePropertyChangedCallback));

private static void VisualStatePropertyChangedCallback(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
var ctrl = s as Control;
if (ctrl == null)
throw new InvalidOperationException("This attached property only supports types derived from Control.");

VisualStateManager.GoToState(ctrl, e.NewValue.ToString(), true);
}

public static string GetVisualStateProperty(DependencyObject obj)
{
return (string)obj.GetValue(VisualStatePropertyProperty);
}

public static void SetVisualStateProperty(DependencyObject obj, string value)
{
obj.SetValue(VisualStatePropertyProperty, value);
}
}
}
You can bind your view:
<UserControl x:Class="StateHelperSL.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StateHelperSL"
local:VisualStateHelper.VisualStateProperty="{Binding State, Mode=OneWay}"
mc:Ignorable="d">

<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Gone">
<Storyboard>
<DoubleAnimation To="1000"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)"
Storyboard.TargetName="AnimatableButton"
d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel Margin="50">
<Button x:Name="AnimatableButton" Click="ButtonBase_OnClick" Height="30" Content="Click me">
<Button.RenderTransform>
<CompositeTransform/>
</Button.RenderTransform>
</Button>

</StackPanel>
</Grid>
</UserControl>
View code behind:
using System.Windows;

namespace StateHelperSL
{
public partial class MainPage
{
private readonly ViewModel vm;

public MainPage()
{
this.InitializeComponent();
this.vm = new ViewModel();
this.DataContext = this.vm;
}

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
vm.State = "Gone";
}
}
}

To your view model:


namespace StateHelperSL
{
public class ViewModel : PropertyChangedBase
{
private string state;

public ViewModel()
{
State = "Normal";
}

public string State
{
get { return this.state; }
set
{
if (state == value) return;
this.state = value;
NotifyOfPropertyChange("State");
}
}
}
}

Wednesday 20 November 2013

ActualSizeProxy

/// <summary>
/// Allows binding to a container (or other framework element) height or width
///
/// Example:
///     <UserControl.Resources>
///          <utils:ActualSizePropertyProxy Element="{Binding ElementName=SellPanel}"  x:Name="SellPanelSizeProxy"/>
///     </UserControl.Resources>    
///
///     <TextBlock Width="{Binding ElementName=SellPanelSizeProxy, Path=ActualWidthValue}" />
///
/// </summary>
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
    public static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
        "Element",
        typeof(FrameworkElement),
        typeof(ActualSizePropertyProxy),
        new PropertyMetadata(null, OnElementPropertyChanged));
    public event PropertyChangedEventHandler PropertyChanged;
    public FrameworkElement Element
    {
        get { return (FrameworkElement)this.GetValue(ElementProperty); }
        set { this.SetValue(ElementProperty, value); }
    }
    public double ActualHeightValue
    {
        get { return this.Element == null ? 0 : this.Element.ActualHeight; }
    }
    public double ActualWidthValue
    {
        get { return this.Element == null ? 0 : this.Element.ActualWidth; }
    }
    private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((ActualSizePropertyProxy)d).OnElementChanged(e);
    }
    private void OnElementChanged(DependencyPropertyChangedEventArgs e)
    {
        var oldElement = (FrameworkElement)e.OldValue;
        var newElement = (FrameworkElement)e.NewValue;
        newElement.SizeChanged += this.ElementSizeChanged;
        if (oldElement != null)
        {
            oldElement.SizeChanged -= this.ElementSizeChanged;
        }
        this.NotifyPropChange();
    }
    private void ElementSizeChanged(object sender, SizeChangedEventArgs e)
    {
        this.NotifyPropChange();
    }
    private void NotifyPropChange()
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
            this.PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
        }
    }
}

DataContextProxy

using System.Windows;
using System.Windows.Data;
 
namespace Utilities
{
    /// <summary>
    /// Allows binding to a viewmodel from within a data/contenttemplate that is bound to something else.
    ///
    /// Example:
    ///     <UserControl.Resources>
    ///         <DataContextProxy x:Key="dataContextProxy" />
    ///     </UserControl.Resources>    
    ///     ...
    ///     <DataTemplate>
    ///         <Button Content="Delete"
    ///                 Command="{Binding Source={StaticResource dataContextProxy}, Path=DataSource.DeleteClass}" 
    ///                 CommandParameter="{Binding}" />
    ///     </DataTemplate>
    ///
    /// </summary>
    public class DataContextProxy : FrameworkElement
    {
        public DataContextProxy()
        {
            this.Loaded += OnLoaded;
        }
        public string BindingPropertyName { get; set; }
        public BindingMode BindingMode { get; set; }
        public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(object), typeof(DataContextProxy), null);
        public object DataSource
        {
            get { return GetValue(DataSourceProperty); }
            set { SetValue(DataSourceProperty, value); }
        }
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var binding = new Binding();
            if (!string.IsNullOrEmpty(BindingPropertyName))
            {
                binding.Path = new PropertyPath(BindingPropertyName);
            }
            binding.Source = DataContext;
            binding.Mode = BindingMode;
            SetBinding(DataSourceProperty, binding);
            // this.Loaded -= this.OnLoaded;
        }
    }
}

Sunday 3 November 2013

AppBar Wizard buttons

I like how Telerik use an App bar, which is always visible, to allow navigation through the application.

image

image

Saturday 2 November 2013

Fluid Dialog Wizard for Silverlight

My Fluid Dialog Wizard is a copy from Visual Studio 2012 setup dialog:

Features plenty of cool stuff: my Modern UI tab control, plenty of visual state manager examples with storyboards, animations, render transforms and transitions, behaviours, draggable dialog, styles, a clip attached property (that forces a panel to clip it’s contents),

Source:    https://github.com/stevenh77/FluidDialogWizard

Clean.bat

dir /B /S "bin" /AD >a.list & for /F "delims=" %%a in (a.list) DO (attrib -H "%%a" & rd /Q /S "%%a")

dir /B /S "obj" /AD >b.list & for /F "delims=" %%a in (b.list) DO (attrib -H "%%a" & rd /Q /S "%%a")

dir /B /S "_ReSharper*" /AD >c.list & for /F "delims=" %%a in (c.list) DO (attrib -H "%%a" & rd /Q /S "%%a")

dir /B /S "Debug" /AD >d.list & for /F "delims=" %%a in (d.list) DO (attrib -H "%%a" & rd /Q /S "%%a")

dir /B /S "x64" /AD >e.list & for /F "delims=" %%a in (e.list) DO (attrib -H "%%a" & rd /Q /S "%%a")

dir /B /S "AppPackages" /AD >f.list & for /F "delims=" %%a in (f.list) DO (attrib -H "%%a" & rd /Q /S "%%a")

dir /B /S "ipch" /AD >g.list & for /F "delims=" %%a in (g.list) DO (attrib -H "%%a" & rd /Q /S "%%a")

attrib -H /S *.suo
del /Q /S *.suo
attrib -H /S *.*sdf
del /Q /S *.*sdf

rem GOTO EOF

del a.list
del b.list
del c.list
del d.list
del e.list
del f.list
del g.list

:EOF

ModernUI Tab Control

This control gives you a ModernUI look and feel for a menu with animated slide in effect when the menu changes.

Source available here:  https://github.com/stevenh77/ModernUITabControl/