Saturday, May 29, 2010

Useful Snippets of Xaml

Here's a sample project a team member worked on as an exercise. The object was to write a search interface and come up with a good user interface.  This example shows how to highlight text in a string based on the search criteria, a nice data template, and changing the visual selection queue. 


     
 

<Window 
    x:Class="WidgetSearch3000.Window1" 
    x:Name="MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:local="clr-namespace:WidgetSearch3000" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    DataContext="{Binding ElementName=MainWindow}" 
    Height="300" 
    Icon="SearchIcon.png" 
    Title="Widget Searcher 3001" 
    Width="300">

    <Window.Resources>
        <local:TextFormatterConverter x:Key="TextFormatter" />

        <Style 
            x:Key="FoundTextStyle" 
            TargetType="{x:Type Run}">
            <Setter Property="Foreground" Value="Red" />
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="Run.TextDecorations" Value="Underline" />
        </Style>

        <Style 
            x:Key="searchTextBox" 
            TargetType="TextBox">
            <Setter Property="FontSize" Value="12pt" />
            <Setter Property="FontFamily" Value="Tahoma" />
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="Template">
                <Setter.Value>
                    <!-- This is the custom control template we will apply to the TextBox -->

                    <ControlTemplate TargetType="TextBox">
                        <Border 
                            Background="WhiteSmoke" 
                            BorderBrush="LightSteelBlue" 
                            BorderThickness="6" 
                            CornerRadius="7" 
                            Padding="7">
                            <Grid>
                                <ScrollViewer x:Name="PART_ContentHost" />
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <DataTemplate x:Key="ContactTemplate">
            <Grid Margin="5, 5, 5, 5">
                <Grid.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform 
                            ScaleX="1" 
                            ScaleY="1" />
                    </TransformGroup>
                </Grid.RenderTransform>
                <Border 
                    x:Name="Grid" 
                    BorderBrush="Black" 
                    BorderThickness="1.5" 
                    CornerRadius="10" 
                    Grid.Row="1" 
                    Height="50" 
                    Padding="5" 
                    VerticalAlignment="Stretch">
                    <Border.Background>
                        <SolidColorBrush 
                            x:Name="HighlightBrushName" 
                            Color="WhiteSmoke" />
                    </Border.Background>
                    <TextBlock 
                        x:Name="Text" 
                        FontSize="15" 
                        HorizontalAlignment="Stretch" 
                        VerticalAlignment="Center">
                        <TextBlock.IsEnabled>
                            <MultiBinding Converter="{StaticResource TextFormatter}">
                                <Binding Path="FullName" />
                                <Binding 
                                    ElementName="tSearchString" 
                                    Path="Text" />
                                <Binding RelativeSource="{RelativeSource Self}" />
                                <Binding Source="{StaticResource FoundTextStyle}" />
                            </MultiBinding>
                        </TextBlock.IsEnabled>
                    </TextBlock>
                </Border>
            </Grid>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
                    <Setter Property="BitmapEffect" TargetName="Grid">
                        <Setter.Value>
                            <DropShadowBitmapEffect />
                        </Setter.Value>
                    </Setter>
                    <Setter Property="Foreground" TargetName="Text" Value="Black" />
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <ColorAnimation 
                                    Duration="00:00:01" 
                                    Storyboard.TargetName="HighlightBrushName" 
                                    Storyboard.TargetProperty="Color" 
                                    To="#FFFDFF2F" />
                                <DoubleAnimation 
                                    Duration="00:00:00.50" 
                                    Storyboard.TargetName="Grid" 
                                    Storyboard.TargetProperty="Height" 
                                    To="60" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <ColorAnimation 
                                    Duration="00:00:00.50" 
                                    Storyboard.TargetName="HighlightBrushName" 
                                    Storyboard.TargetProperty="Color" 
                                    To="WhiteSmoke" />
                                <DoubleAnimation 
                                    Duration="00:00:00.50" 
                                    Storyboard.TargetName="Grid" 
                                    Storyboard.TargetProperty="Height" 
                                    To="50" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="10" />
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid 
            Grid.Row="1" 
            VerticalAlignment="Stretch">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="60" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="30" />
            </Grid.ColumnDefinitions>
            <Label 
                x:Name="label1" 
                FontSize="12" 
                FontWeight="Bold" 
                Grid.Column="0" 
                Height="28">
                Search:
            </Label>
            <TextBox 
                x:Name="tSearchString" 
                Grid.Column="1" 
                HorizontalAlignment="Stretch" 
                Style="{StaticResource searchTextBox}" 
                TextChanged="tSearchString_TextChanged" />
        </Grid>
        <GroupBox 
            x:Name="groupBox1" 
            Grid.Row="2" 
            Header="Results" 
            VerticalAlignment="Stretch">
            <ListBox 
                x:Name="listOfPeople" 
                HorizontalContentAlignment="Stretch" 
                ItemsSource="{Binding FoundPeople}" 
                ItemTemplate="{StaticResource ContactTemplate}" 
                Padding="3" 
                ScrollViewer.VerticalScrollBarVisibility="Visible">
                <ListBox.Resources>
                    <SolidColorBrush 
                        x:Key="{x:Static SystemColors.HighlightBrushKey}" 
                        Color="Transparent" />
                </ListBox.Resources>
            </ListBox>
        </GroupBox>
    </Grid>

</Window>

Code behind: (This is not a demonstration of how to format your code behind nor to write clean code!) :-)


namespace WidgetSearch3000 {
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Collections.ObjectModel;

    public class TextFormatterConverter : IMultiValueConverter {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            var name = values[0] as String;
            if (name == null) {
                return true;
            }

            var nameLower = name.ToLower();

            var search = values[1] as String;
            if (search == null) {
                return true;
            }
            var searchLower = search.ToLower();

            var t = values[2] as TextBlock;
            if (t == null) {
                return true;
            }
            var runStyle = values[3] as Style;
            if (runStyle == null) {
                return true;
            }
            t.Inlines.Clear();

            Run run;
            int index = nameLower.IndexOf(searchLower);
            if (!searchLower.Equals("")) {
                while (index != -1) {

                    if (index != 0) {
                        run = new Run();
                        //run.Foreground = Brushes.Black;
                        run.Text = name.Substring(0, index);

                        t.Inlines.Add(run);

                    }
                    run = new Run();
                    run.Text = name.Substring(index, search.Length);
                    run.Style = runStyle;
                    t.Inlines.Add(run);
                    name = name.Substring(index + search.Length);
                    nameLower = nameLower.Substring(index + search.Length);
                    index = nameLower.IndexOf(searchLower);

                }
            }
            if (!name.Equals("")) {
                run = new Run();
                //run.Foreground = Brushes.Black;
                run.Text = name;
                t.Inlines.Add(run);

            }

            return true;


            //}
            //return string.Format("this is a test: {0} {1}", values[0], values[1]);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
            throw new NotImplementedException();
        }

        #endregion
    }
    
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window {
        private List<Person> m_lsPeople;
        private ObservableCollection<Person> m_lsFound = new ObservableCollection<Person>();
        public Window1() {
            InitializeComponent();
            //m_lsFound = new ObservableCollection<Person>();
            m_lsPeople = new List<Person>();
            m_lsPeople.Add(new Person("Paul", "Machinroe"));
            m_lsPeople.Add(new Person("Frank", "Smith"));
            m_lsPeople.Add(new Person("Joe", "Yakk"));
            m_lsPeople.Add(new Person("Grant", "Wellington"));
            m_lsPeople.Add(new Person("Lucy", "Fiat"));
            m_lsPeople.Sort();
            //DataContext = this;
            Search();
        }

        public void Search() {
            //   m_lsFound.Clear();
            Boolean hasAdded = false;
            foreach (Person person in m_lsPeople) {

                if (!person.Contains(tSearchString.Text)) {
                    m_lsFound.Remove(person);
                } else if (!m_lsFound.Contains(person)) {
                    hasAdded = false;
                    for (int i = 0; i < m_lsFound.Count; i++) {
                        if (person.CompareTo(m_lsFound[i]) < 0) {

                            m_lsFound.Insert(i, person);
                            hasAdded = true;
                            break;
                        }

                    }
                    if (!hasAdded) {
                        m_lsFound.Add(person);
                    }

                }
            }

        }

        public ObservableCollection<Person> FoundPeople {
            get {
                return m_lsFound;
            }
            set {
                m_lsFound = value;
            }
        }

        private void tSearchString_TextChanged(object sender, TextChangedEventArgs e) {
            Search();
        }
    }
}

Thursday, May 27, 2010

C# 4 Dynamic Keyword

What is it?

As we all know C# is a strongly typed language, and as we also know late binding 99.99% of the time is a really bad idea (performance and maintainability).  To a certain extent the dynamic keyword is a step away from a strongly typed variable type. So what is the new dynamic keyword?

Lets have a look at an example:

namespace DynamicKeyword {
    using System;

    public class Program {
        public static void Main(string[] args) {
            Console.WriteLine("Test1");

            dynamic variant = 123.44; 
            Console.WriteLine(variant);
            Console.WriteLine("    of type " + variant.GetType().Name);
            Console.WriteLine("    9 + variant = " + (9 + variant));

            variant = "hello world";  // This compiles ok too...
            Console.WriteLine(variant);
            Console.WriteLine("    of type " + variant.GetType().Name);
            Console.WriteLine("    9 + variant = " + (9 + variant));
            Console.WriteLine("    " + variant.Length); // This compiles even though the Length property doesnt appear on the intellisense list.
        }
    }
}

The above code compiles and runs producing the following output.
Things to note:
  1. Assigning a double to a dynamic makes the variable type a double.  
  2. Adding a number to it does proper number addition, not concatenation.
  3. Assigning a string to it compiles, and morphs the type to a string!
  4. Use of the + operation now performs string concatenation.
  5. We can access and call methods on the string type that were not there before when it was double.

Basically the dynamic keyword bypasses all of C#'s type checking.


You will still get exceptions at runtime to show inappropriate operations.
Consider this....


namespace DynamicKeyword {
    using System;

    public class Program {
        public static void Main(string[] args) {
            Console.WriteLine("Test1");

            dynamic variant = 123.44; 
            Console.WriteLine(variant.Foo()); // Will compile but not work
        }
    }
}

Output at runtime:
So type checking goes out the window.  Sounds like an extremely dangerous tool to make your fellow developers go nuts debugging your carefully job-secured code.
Why is this potentially useful while making a mockery of strongly typed code?
  • It could be useful for highly dynamic interfaces that are constantly changing.
  • It could be useful for reinstantiation of json objects... See Pete's blog.
  • Josh Smith suggested a using a proxy pattern to access any WPF UI element and using that element's dispatcher if necessary.
  • Makes COM API's easier to consume. See this Msdn Article.

What is the difference between var and dynamic?

Easy.  Var is strongly typed dynamic is not. Dynamic bypasses all of C#'s type checks at intelli-sense-time and at compile time.  
Var simple is a macro for the compiler to insert the appropriate type name at compile time.  The type is inferred from the right hand side of the assignment (=) operator.
Var makes code far easier to refactor and saves you typing.  If you want to know the type simply hover the mouse over the var keyword it will tell you.


namespace DynamicKeyword {
    using System;

    public class Program {
        public static void Main(string[] args) {
            Console.WriteLine("Test1");

            dynamic variant = "hello world";
            Console.WriteLine(variant);
            Console.WriteLine("    of type " + variant.GetType().Name);
            variant = 123.44;
            Console.WriteLine(variant);
            Console.WriteLine("    of type " + variant.GetType().Name);

            Console.WriteLine("Test2");
            var notVariant = "Hello World Again!";
            Console.WriteLine(notVariant);
            notVariant = 123.45; // Does not compile
        }
    }
}

As a nice side benefit, if you declare all your variables together they all line up when using var. 

Resistance is futile.


Ben Rees - Jun 8, 2010 2:37 AM
Coincidently MSDN Mag has a good article on the Dynamic keyword this month also: http://msdn.microsoft.com/en-us/magazine/ff714583.aspx


Interesting idea to use it for COM interop, something I have not had to use a great deal, but does cover a gap comparing VB.Net to C# (with Vb's Option Strict feature).