DCSIMG
March 2008 - Posts - Just code - Tamir Khason

March 2008 - Posts

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/quick-silverlight-tip-how-to-make-tooltip-works-in-beta-1/]


If you paid attention, there is neither ToolTip  nor ToolTipService control in MSDN library, however, you can see it via intellisense in Visual Studio. If you'll try to use it, you'll get AG_E_PARSER_PROPERTY_NOT_FOUND XamlParser exception. The reason is, that ToolTip as well as ToolTip service come from extended controls set. Thus in order to use it, you'll have to make explicit reference to the dll.  So, following will work

<UserControl x:Class="FloraRIA.Page"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:extended="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"      

.....

<extended:ToolTipService.ToolTip>
                   <ToolTip Content="Hello world!"/>
</extended:ToolTipService.ToolTip>

Happy coding

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/building-custom-user-control-in-silverlight-20-how-to-build-code-snippet-for-vs-as-bonus/]


Do you remember, that we have "go-live" for Silverlight 2.0 and already have build machines configured? Now it's time to build your very own custom control. Today, we'll build Range Slider.

image

What is range slider? Range slider is a control, that lets you input two values, typically an upper and a lower bound. Normal slider just lets you input one value. So, we have new behavior here, thus we'll have to build our own control without reusing existing one.

But, before we'll start, we'll build code snippet for Visual Studio, that allows us quickly build Dependency Property. Due to fact, that existing snippets (propdp, etc) do not fit Silverlight DP creation pattern. In Silverlight, we have no DependencyPropertyMetadata, so the registration pattern will be

DependencyProperty.Register(
                  "$property$",
                  typeof($type$),
                  typeof($ownerclass$),
                  On$property$PropertyChanged);

Open new XML file, read this and let's start. First of all, general stuff line who am I, what's the short string for the snippet, etc.

<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Define a DependencyProperty for Silverlight application</Title>
      <Shortcut>propds</Shortcut>
      <Description>
        Code snippet for a property using DependencyProperty as the backing store and a Handler for the DependencyPropertyChanged event
      </Description>
      <Author>Tamir Khason</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="true">
          <ID>type</ID>
          <ToolTip>Property Type</ToolTip>
          <Default>int</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="true">
          <ID>property</ID>
          <ToolTip>Property Name</ToolTip>
          <Default>MyProperty</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="false">
          <ID>ownerclass</ID>
          <ToolTip>
            The owning class of this Property. Typically the class that it is declared in.
          </ToolTip>
          <Default>ClassNamePlaceholder</Default>
          <Function>ClassName()</Function>
        </Literal>
      </Declarations>

Then the interesting stuff. Where my properties and variables.

<Code Language="csharp">
        <![CDATA[
#region $property$

/// <summary>
/// Gets or sets the $property$ possible Value of the $type$ object.
/// </summary>
public $type$ $property$
{
    get { return ($type$)GetValue($property$Property); }
    set { SetValue($property$Property, value); }
}

/// <summary>
/// Identifies the $property$ dependency property.
/// </summary>
public static readonly DependencyProperty $property$Property =
            DependencyProperty.Register(
                  "$property$",
                  typeof($type$),
                  typeof($ownerclass$),
                  On$property$PropertyChanged);

/// <summary>
/// $property$Property property changed handler.
/// </summary>
/// <param name="d">$ownerclass$ that changed its $property$.</param>
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
private static void On$property$PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  $ownerclass$ _$ownerclass$ = d as $ownerclass$;
  if (_$ownerclass$!=null)
  {
    //TODO: Handle new value.
  }
}
#endregion $property$
$end$]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

We done, you can either download the ready snippet for Silverlight Dependency Property creation here. All you have to do is to put it into %MY DOCUMENTS%\Visual Studio 2008\Code Snippets\Visual C#\My Code Snippets or \Program Files\Microsoft Visual Studio 9.0\VC#\Snippets\1033\Visual C# directory. Now, we can use "propdps" to quickly define Dependency Property for Silverlight class.

Well done, and now we can start writing. First of all, we'll create base logic class to encapsulate Minimum, Maximum, ValueLow, ValueHigh properties and ValueChange routed events. The only object we can derive from in order to use Dependency Property is Control. So we'll do it.

public abstract class DoubleRangeBase : Control
    {

Building Minimum property...

#region Minimum

        /// <summary>
        /// Gets or sets the Minimum possible Value of the double object.
        /// </summary>
        public double Minimum
        {
            get { return (double)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }

        /// <summary>
        /// Identifies the Minimum dependency property.
        /// </summary>
        public static readonly DependencyProperty MinimumProperty =
                    DependencyProperty.Register(
                          "Minimum",
                          typeof(double),
                          typeof(DoubleRangeBase),
                          OnMinimumChanged);

        /// <summary>
        /// MinimumProperty property changed handler.
        /// </summary>
        /// <param name="d">LowHighRangeBase that changed its Minimum.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param>
        private static void OnMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DoubleRangeBase _LowHighRangeBase = d as DoubleRangeBase;
            if (_LowHighRangeBase != null)
            {
                _LowHighRangeBase._requestedMin = (double)e.NewValue;
                _LowHighRangeBase.CoerceMaximum();
                _LowHighRangeBase.CoerceValues();
                _LowHighRangeBase.OnMinimumChanged((double)e.OldValue, (double)e.NewValue);    
            }
        }
        #endregion

Isn't it nice snippet? :) Now events

public event RoutedPropertyChangedEventHandler<double> ValueLowChanged;
public event RoutedPropertyChangedEventHandler<double> ValueHighChanged;
public event RoutedPropertyChangedEventHandler<ValueChangedEventArgs> ValueChanged;

Handlers... Some virtual and some not

protected virtual void OnMaximumChanged(double oldMaximum, double newMaximum)
        {
        }

protected virtual void OnValueChanged(double oldValue, double newValue, ValueChangeType type)
        {
            RoutedPropertyChangedEventHandler<ValueChangedEventArgs> handler = ValueChanged;
            if (handler != null)
            {
                ValueChangedEventArgs oVal = new ValueChangedEventArgs(oldValue,type);
                ValueChangedEventArgs nVal = new ValueChangedEventArgs(newValue, type);
                handler(this, new RoutedPropertyChangedEventArgs<ValueChangedEventArgs>(oVal, nVal));
            }
        }

Coerces (that we missing in Silverlight, and as for my, this approach much better, then lazy counters, used in extended Silverlight controls)...

private void CoerceValues()
        {
            // Ensure it's a valid value
            if (!IsValidDoubleValue(_requestedValueLow) | !IsValidDoubleValue(_requestedValueHigh) | !IsValidDoubleValue(_requestedMax) | !IsValidDoubleValue(_requestedMin))
            {
                throw new ArgumentException("Invalid double value", MinimumProperty.ToString());
            }

            double minimum = Minimum;
            double maximum = Maximum;
            double valueHigh = ValueHigh;
            double valueLow = ValueLow;

            if (valueHigh < minimum)
            {
                SetValue(ValueHighProperty, minimum);
                return;
            }
            if (valueHigh > maximum)
            {
                SetValue(ValueHighProperty, maximum);
                return;
            }

            if (valueLow < minimum)
            {
                SetValue(ValueLowProperty, minimum);
                return;
            }
            if (valueLow > maximum)
            {
                SetValue(ValueLowProperty, maximum);
                return;
            }

            if (_requestedValueHigh < valueLow)
                _requestedValueHigh = valueLow;

            if (_requestedValueHigh > maximum)
                _requestedValueHigh = maximum;

            if (_requestedValueHigh < minimum)
                _requestedValueHigh = minimum;

            if (_requestedValueHigh != valueHigh)
            {
                SetValue(ValueHighProperty, _requestedValueHigh);
                return;
            }

            if (_requestedValueLow > valueHigh)
                _requestedValueLow = valueHigh;

            if (_requestedValueLow > maximum)
                _requestedValueLow = maximum;

            if (_requestedValueLow < minimum)
                _requestedValueLow = minimum;

            if (_requestedValueLow != valueLow)
            {
                SetValue(ValueLowProperty, _requestedValueLow);
                return;
            }

        }

And we done... Now the turn of the original class. We want to be able to template it, so, let's add first generic.xaml with control template of our range slider. This is pretty simple, but there are some chatchus with it. One, you should explicitly specify the assembly, your control resides in. Another, that implicit styles do not work in Silverlight, so you should specify it. Other stuff is rather similar to WPF (even namespace)

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:Sharpsoft.Controls;assembly=Sharpsoft.Controls"
    >

....

<Grid x:Name="HorizontalTemplateElement">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <!-- Track Layer -->
                            <Rectangle Stroke="Black" StrokeThickness="0.5" Fill="{TemplateBinding BackBrush}" Grid.Column="0" Grid.ColumnSpan="5" Height="3" Margin="5,0,5,0" />

                            <!-- Fillters + Thumb -->
                            <Rectangle x:Name="HorizontalLowFillerElement" Fill="Transparent" Grid.Column="0" Height="3" />
                            <Thumb Style="{TemplateBinding ThumbStyle}" x:Name="HorizontalLowThumbElement" Grid.Column="1"/>
                            <Rectangle x:Name="HorizontalCenterFillerElement" Fill="{TemplateBinding SelectionBrush}" Grid.Column="2" Height="3" />
                            <Thumb Style="{TemplateBinding ThumbStyle}" x:Name="HorizontalHighThumbElement" Grid.Column="3" />
                            <Rectangle x:Name="HorizontalHighFillerElement" Fill="Transparent" Grid.Column="4" Height="3" />
                        </Grid>

We want to be able to change an orientation of the range slider, so adding another section for vertical alignment.

<Grid x:Name="VerticalTemplateElement" Visibility="Collapsed">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>

                            <!-- Track Layer -->
                            <Rectangle Stroke="Black" StrokeThickness="0.5" Fill="{TemplateBinding BackBrush}" Grid.Row="0" Grid.RowSpan="5" Width="3" Margin="0,5,0,5" />

                            <!-- Fillters + Thumb -->
                            <Rectangle x:Name="VerticalLowFillerElement" Grid.Row="4" Width="3" />
                            <Thumb Style="{TemplateBinding ThumbStyle}" x:Name="VerticalLowThumbElement" Grid.Row="3" />
                            <Rectangle x:Name="VerticalCenterFillerElement" Fill="{TemplateBinding SelectionBrush}" Grid.Row="2" Width="3" />
                            <Thumb Style="{TemplateBinding ThumbStyle}" x:Name="VerticalHighThumbElement" Grid.Row="1" />
                            <Rectangle x:Name="VerticalHighFillerElement" Grid.Row="0" Width="3" />
                        </Grid>

Now, when we done, we can start with the code of the control. Don't forget about TemplatePart attributes....

[TemplatePart(Name = RangeSlider.ElementHorizontalHighFillerName, Type = typeof(Rectangle))]
    [TemplatePart(Name = RangeSlider.ElementHorizontalLowFillerName, Type = typeof(Rectangle))]
    [TemplatePart(Name = RangeSlider.ElementHorizontalCenterFillerName, Type = typeof(Rectangle))]
    [TemplatePart(Name = RangeSlider.ElementVerticalTemplateName, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = RangeSlider.ElementVerticalLowThumbName, Type = typeof(Thumb))]
    [TemplatePart(Name = RangeSlider.ElementVerticalHighThumbName, Type = typeof(Thumb))]
...

public class RangeSlider : DoubleRangeBase
    {

Don't forget to create and register those properties in backend.

/// <summary>
        /// Horizontal low filler
        /// </summary>
        internal virtual Rectangle ElementHorizontalLowFiller { get; set; }
        internal const string ElementHorizontalLowFillerName = "HorizontalLowFillerElement";

        /// <summary>
        /// Vertical template root
        /// </summary>
        internal virtual FrameworkElement ElementVerticalTemplate { get; set; }
        internal const string ElementVerticalTemplateName = "VerticalTemplateElement";

Internally subscribing to mouse and keyboard events in constructor...

public RangeSlider()
        {
            Minimum = 0;
            Maximum = 100;
            ValueHigh = 80;
            ValueLow = 20;

            IsTabStop = true;
            IsEnabled = true;
            Orientation = Orientation.Horizontal;
            GotFocus += delegate { IsFocused = true; };
            LostFocus += delegate { IsFocused = false; };
            KeyDown += delegate(object sender, KeyEventArgs e) { OnKeyPressed(e); };
            MouseEnter += delegate(object sender, MouseEventArgs e) { OnMouseEnter(e); };
            MouseLeave += delegate(object sender, MouseEventArgs e) { OnMouseLeave(e); };
            MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e) { OnMouseLeftButtonDown(e); };
            MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e) { OnMouseLeftButtonUp(e); };
            SizeChanged += delegate { UpdateTrackLayout(); };
        }

And overriding OnApplyTemplate methods...

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

           ElementRoot = GetTemplateChild(ElementRootName) as FrameworkElement;
           ElementHorizontalTemplate = GetTemplateChild(ElementHorizontalTemplateName) as FrameworkElement;
           ElementHorizontalLowThumb = GetTemplateChild(ElementHorizontalLowThumbName) as Thumb;
           ElementHorizontalHighThumb = GetTemplateChild(ElementHorizontalHighThumbName) as Thumb;
          

Don't forget about Drag events.

if (ElementHorizontalLowThumb != null)
            {
                ElementHorizontalLowThumb.DragStarted += delegate(object sender, DragStartedEventArgs e) { OnLowThumbDragStarted(e); };
                ElementHorizontalLowThumb.DragDelta += delegate(object sender, DragDeltaEventArgs e) { OnLowThumbDragDelta(e); };
            }

Let's add some UI related properties like Orientation, IsFocused, IsEnabled, SelectionBrush, BackBrush, etc... Even ThumbStyle (do you remember, it does not work implicitly)

#region ThumbStyle

        /// <summary>
        /// Gets or sets the ThumbStyle possible Value of the Style object.
        /// </summary>
        public Style ThumbStyle
        {
            get { return (Style)GetValue(ThumbStyleProperty); }
            set { SetValue(ThumbStyleProperty, value); }
        }

        /// <summary>
        /// Identifies the ThumbStyle dependency property.
        /// </summary>
        public static readonly DependencyProperty ThumbStyleProperty =
                    DependencyProperty.Register(
                          "ThumbStyle",
                          typeof(Style),
                          typeof(RangeSlider),
                          null);

        #endregion ThumbStyle

Ah, snippets is cool thing! Now we can override virtual methods to handle it

protected override void OnValueHighChanged(double oldValue, double newValue)
       {
           base.OnValueHighChanged(oldValue, newValue);
           if (ElementRoot != null)
           {
               UpdateTrackLayout();
           }
       }

Mouse events

private void OnMouseEnter(MouseEventArgs e)
        {
            e.Handled = true;
            IsMouseOver = true;
            if ((Orientation == Orientation.Horizontal && ElementHorizontalLowThumb != null && !ElementHorizontalLowThumb.IsDragging && ElementHorizontalHighThumb != null && !ElementHorizontalHighThumb.IsDragging) ||
                (Orientation == Orientation.Vertical && ElementVerticalLowThumb != null && !ElementVerticalLowThumb.IsDragging && ElementVerticalHighThumb != null && !ElementVerticalHighThumb.IsDragging))
            {
                UpdateVisualState();
            }
        }

Stubs for possible animations...

internal void ChangeVisualState(Storyboard state)
        {
            Storyboard previousState = _currentState;
            if (state == previousState)
            {
                return;
            }

            if (state != null)
            {
                if (previousState != null)
                {
                    previousState.Stop();
                }
                _currentState = state;
                state.Begin();
            }
        }

And position calculations at the end

private void OnLowThumbDragDelta(DragEventArgs e)
        {
            double offset = 0;

            if (Orientation == Orientation.Horizontal && ElementHorizontalLowThumb != null)
            {
                offset = e.HorizontalOffset / (ActualWidth - ElementHorizontalLowThumb.ActualWidth) * (Maximum - Minimum);
            }
            else if (Orientation == Orientation.Vertical && ElementVerticalLowThumb != null)
            {
                offset = -e.VerticalOffset / (ActualHeight - ElementVerticalLowThumb.ActualHeight) * (Maximum - Minimum);
            }

            if (!double.IsNaN(offset) && !double.IsInfinity(offset))
            {
                _dragValue += IsDirectionReversed ? -offset : offset;

                double newValue = Math.Min(Maximum, Math.Max(Minimum, _dragValue));

                if (newValue != ValueLow)
                {
                    ValueLow = newValue;
                }               
            }
        }

The only thing we have to do is to treat those measurement and layout calculations

protected virtual void UpdateTrackLayout()
        {
            double maximum = Maximum;
            double minimum = Minimum;
            double valueLow = ValueLow;
            double valueHigh = ValueHigh;

            Grid templateGrid = (Orientation == Orientation.Horizontal) ? (ElementHorizontalTemplate as Grid) : (ElementVerticalTemplate as Grid);
            if (templateGrid != null)
            {
                if (Orientation == Orientation.Horizontal && templateGrid.ColumnDefinitions != null &&
                    templateGrid.ColumnDefinitions.Count == 5)
                {
                    templateGrid.ColumnDefinitions[0].Width = new GridLength(1, IsDirectionReversed ? GridUnitType.Star : GridUnitType.Auto);
                    templateGrid.ColumnDefinitions[4].Width = new GridLength(1, IsDirectionReversed ? GridUnitType.Auto : GridUnitType.Star);                   
                }
                else if (Orientation == Orientation.Vertical && templateGrid.RowDefinitions != null &&
                    templateGrid.RowDefinitions.Count == 5)
                {
                    templateGrid.RowDefinitions[0].Height = new GridLength(1, IsDirectionReversed ? GridUnitType.Auto : GridUnitType.Star);
                    templateGrid.RowDefinitions[4].Height = new GridLength(1, IsDirectionReversed ? GridUnitType.Star : GridUnitType.Auto);                   
                }
            }

            if (Orientation == Orientation.Horizontal && ElementHorizontalCenterFiller != null &&
                ElementHorizontalLowFiller != null && ElementHorizontalLowThumb != null &&               
                ElementHorizontalHighFiller != null && ElementHorizontalHighThumb != null)
            {
                ElementHorizontalLowFiller.Width = (valueLow - minimum) * (ActualWidth - ElementHorizontalHighThumb.ActualWidth - ElementHorizontalLowThumb.ActualWidth) / (maximum - minimum);
                ElementHorizontalCenterFiller.Width = (valueHigh - valueLow) * (ActualWidth - ElementHorizontalHighThumb.ActualWidth - ElementHorizontalLowThumb.ActualWidth) / (maximum - minimum);
                ElementHorizontalHighFiller.Width = (maximum - valueHigh) * (ActualWidth - ElementHorizontalHighThumb.ActualWidth - ElementHorizontalLowThumb.ActualWidth) / (maximum - minimum);

            }
            else if (Orientation == Orientation.Vertical && ElementVerticalCenterFiller != null &&
                ElementVerticalLowFiller != null && ElementVerticalLowThumb != null &&
                ElementVerticalHighFiller != null && ElementVerticalHighThumb != null)
            {
                ElementVerticalLowFiller.Height = (valueLow - minimum) * (ActualHeight - ElementVerticalLowThumb.ActualHeight - ElementVerticalHighThumb.ActualHeight) / (maximum - minimum);
                ElementVerticalCenterFiller.Height = (valueHigh - valueLow) * (ActualHeight - ElementVerticalLowThumb.ActualHeight - ElementVerticalHighThumb.ActualHeight) / (maximum - minimum);
                ElementVerticalHighFiller.Height = (maximum - valueHigh) * (ActualHeight - ElementVerticalLowThumb.ActualHeight - ElementVerticalHighThumb.ActualHeight) / (maximum - minimum);
            }
        }

...and we done.

Now we can do to our Silverlight project and add the control there. something like this will work

<c:RangeSlider
            ValueHigh="{Binding Value1, Mode=TwoWay}"
            ValueLow="{Binding Value2, Mode=TwoWay}"
            Minimum="50"
            Maximum="250"
            SelectionBrush="Red"
            BackBrush="Blue"
            ThumbStyle="{StaticResource thumb}"
            Grid.ColumnSpan="2"
            />

<c:RangeSlider
            ValueHigh="{Binding Value1, Mode=TwoWay}"
            ValueLow="{Binding Value2, Mode=TwoWay}"
            Grid.RowSpan="3"
            Grid.Column="2"
            Orientation="Vertical"
            />

Of cause you need data in backend to bind to. Silverlight does not support ElementName in binding by now. Thus, you should work with object bindings only.

public class MyObject : INotifyPropertyChanged
    {
        double val1, val2;
        public double Value1 { get { return val1; } set { val1 = value; fireChanged("Value1"); } }
        public double Value2 { get { return val2; } set { val2 = value; fireChanged("Value2"); } }

        #region INotifyPropertyChanged Members

        void fireChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }

As exposed thumb style property, so why not to create cool thumb as well?

<UserControl.Resources>
    <l:MyObject x:Name="obj"/>
    <Style TargetType="Thumb" x:Key="thumb">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Thumb">
                    <Path Data="M3,1 L2,2 L1,1 L2,3 z" Stretch="Fill" Fill="Yellow" Stroke="Black"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Width" Value="20"/>
        <Setter Property="Height" Value="20"/>
    </Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource obj}">

And that it. Now we have out range slider up and working. (You cannot see it from your feed reader. Visit the original page to view)

And the small tip at the end. If you want to register Silverlight MIME extension in your IIS (webhosting, etc), you should use application\x-silverlight-app application type for .xap file extensions.

Have a nice day and be good people.

Source code for this article.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/msbuild-tasks-for-build-machines-for-wpf-and-silverlight/]


Silverlight got go-life, so we should start preparing automatic build machines for new Silverlight projects. Actually, it is not too complicated, however, there are some new tasks should be taken into account.

  • Output type of any Silverlight application is Library
  • New XML node specializing the output type of the library: SilverlightApplication with True value
  • Another XML node, that specializing the output file type XapOutputs with True value. XAP is, actually, ZIP archive of all assemblies, used within the application.
  • XAP file name can be set with XapFilename property
  • We can decide whither create Silverlight manifest file or not with GenerateSilverlightManifest tag (true)
  • The template of the manifest for generation resides in SilverlightManifestTemplate tag, so you can specify the path to xml file for manifest generation&nbsp;
  • The entry point of your application should be set with SilverlightAppEntry property, that specifying the name of class (for most cases it will be $(AppName).App)
  • You can tell the enviroment to generate test web page, hosting your Silverlight control by specifying CreateTestPage=true
  • If you decided to generate test file, you should specify the test page name by setting TestPageFileName
There are some new constants for compile conditions
  • DebugType - the type of debug symbols. Instead of general "full", you should specify "pdbonly" value.
  • NoStdLib defines inclusion of standard libraries with the project (kind of "always copy"). Default = true (not include)
  • NoConfig defines inclusion of config data. Default = true (not include)
Reference set for standard Silverlight application is
  • System.Windows
  • mscorlib
  • system
  • System.Core
  • System.Xml
  • System.Windows.Browser
  • System.Windows.Controls
Those are not standard .NET classes, but special Silverlight dlls, resides in \Program Files\Microsoft Silverlight directory

You can also include your own references by setting Private node of Reference to true. Here the example of such include for Silverlight Extended controls

<Reference Include="System.Windows.Controls.Extended">
<Private>True</Private>
</Reference>

ItemGroup section is rather standard, except of new node SilverlightPage (uses standard MSBuild:CompileXaml generator.

That's all, folks. Happy build machines to you.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/silverlight-20-beta-1-is-alive-how-to-use-it-now/]


As you, probably, know, Silverlight 2.0 got "non-commercial" go-live and how you can start using it for real. But how to do it? Let try to understand.

First of all, you have to remove all previous versions of Silverlight as well as remove Silverlight Alpha tools for Visual Studio. Then, install Runtime andm using chain installer, get Visual Studio 2008 tools for Silverlight 2.0 beta 1. I installs SDK, but does not install documentation. You can get it offline here. If everything went well, you'll have version 2.0.30226.2 installed. As well as inside Visual Studio 2008, you'll get new project type, named Silverlight.



Now, when we have the installation, our next step will be to understand changes in syntax and build one sample program.

First thing, we'll do will be creation of DependencyObject.

public class MyObject : DependencyObject    {       
    public string Title        {           
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }        }
   
    public static readonly DependencyProperty TitleProperty =            DependencyProperty.Register("Title", typeof(string), typeof(MyObject), OnTitlePropertyChanged);

    static void OnTitlePropertyChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)        { }   
}
So far so good, but if you'll try to compile it, you'll get following error: "Error&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp; The type 'System.Windows.DependencyObject' has no constructors defined". What is it? This is beta! In Silverlight 2.0 beta 1, the only object you may directly derrive from is UserControl. So, let's change out MyObject class to inherit from UserControl. Not very nice, but we have no chance.

Now the turn of collection. We can derrive from IList&lt;T&gt; or ICollection&lt;T&gt;, so let's write some code.

public class MyCollection : ObservableCollection<MyObject>
    {
        public MyCollection()
        {
            for (int i = 0; i < 10; i++)
            {
                MyObject o = new MyObject();
                o.Title = string.Format("Item {0}", i);
                base.Add(o);
            }
        }
    }

You should also put it into resources, so inside you Page1.xaml, we'll add following lines.

...
xmlns:l="clr-namespace:SilverlightApplication1"
    Width="400" Height="300">
    <UserControl.Resources>
        <l:MyCollection x:Key="collection"/>
    </UserControl.Resources>

Dont't it looks like WPF? It really is! Let's see if binding works the same way as in WPF

<Grid x:Name="LayoutRoot" Background="White">
            <ListBox ItemsSource="{Binding Source={StaticResource collection}}"/>
    </Grid>

Compile and run - see nothing? There is a reason for it. If you'll change the collection to well-known type collection (e.g. string), you'll see the result. Now you see nothing, 'cos you have no DataTemplate (for some reason reflected type does not appear as well - nu, shojn, beta :) ). So, now we know, that we need DataTemplate. Let's do it

<UserControl.Resources>
        <l:MyCollection x:Key="collection"/>
        <DataTemplate x:Key="template">
            <TextBlock Text="{Binding Title}"/>
        </DataTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <ListBox
            ItemTemplate="{StaticResource template}"
            ItemsSource="{Binding Source={StaticResource collection}}"/>
    </Grid>

Very well. We have simple ListBox, binded to underlying data source. As alternative, you can use DisplayMemberPath="Title" (in our case) to visualize simple data item



So far, so good. Let's add buttons and handlers to our sample program.
First let's create markup for the root grid

<Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

Next, put StackPanel and two buttons inside it.

<StackPanel Grid.Row="1" >
            <Button x:Name="addButton" Content="Add Item" Click="Button_Click"/>
            <Button x:Name="removeButton" Content="Remove Item" Click="Button_Click"/>
    </StackPanel>


And at the end, handlers for those buttons


private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyCollection coll = Resources["collection"] as MyCollection;
            Button b = sender as Button;
            if (b.Name == "addButton")
            {
                MyObject o = new MyObject();
                o.Title = string.Format("Item {0}", coll.Count);
                coll.Insert(0,o);
            }
            else if (b.Name == "removeButton")
            {
                if (coll.Count > 0)
                {
                    coll.RemoveAt(0);
                }
            }
        }


Well, it's absolutely WPF stuff, in spite of the fact, that we are in Silverlight. However, it's not 100% right. We're still missing DataTriggers, some of layout controls, style trigger etc. But it's still in beta, and by now it's looking like in very near future we'll be able to develop the same code and just compile it for different platforms: WPF, WPF XBAP, Silverlight (as for me WPF/E is better name for this version, due to fact, that it's tool close to WPF)

Thank you and be good people. Stay tuned for upcoming articles...

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/binding-to-current-item-external-collection-management-datatemplates-and-datatriggers/]


Today, we'll learn how to manage your collection, binded to ItemsControl externally, how to bind to the current item of the collection and how to use data templates with data triggers to make your UI data driven. So let's start.

image

First of all, we should create the items for the collection and the collection itself. IT's pretty straight-forward by now.

public class RedBlackItem : DependencyObject
    {
        public RedBlackItem(bool isRed)
        {
            this.IsRed = isRed;
        }

        public bool IsRed
        {
            get { return (bool)GetValue(IsRedProperty); }
            set { SetValue(IsRedProperty, value); }
        }
        public static readonly DependencyProperty IsRedProperty =
            DependencyProperty.Register("IsRed", typeof(bool), typeof(RedBlackItem), new UIPropertyMetadata(default(bool)));

    }

public class RedBlackCollection : ObservableCollection<RedBlackItem>
    {
        public RedBlackCollection()
        {
            Random r = new Random();
            for (int i = 0; i < 10; i++)
            {
                base.Add(new RedBlackItem(r.NextDouble() < 0.5));
            }
        }
    }

Next, let's create the instance of our collection in XAML and connect it to the view

<Window.Resources>
    <l:RedBlackCollection x:Key="collection"/>
    <CollectionViewSource Source="{StaticResource collection}" x:Key="data"/>

Actually, if you'll have at least one ItemsControl in your project, you not have to create CollectionView, due to fact, that Items is, actually will be the view. But we want it absolutely UI independent and data-driven, so we'll use CollectionViewSource to manage it.

Now, let's bind the collection view to our Item control

<ListBox ItemsSource="{Binding Source={StaticResource data}}"/>

Running the program right now, will display strings, reflecting the item type. Now we want to know what item was selected and display it in other place. But how to do it? We, actually, have no SelectedItem dependency property. Let try to make WPF to do all the work and bind the same collection to the control, that knows to display only one item. The best candidate for it is ContentControl

<ContentControl Content="{Binding Source={StaticResource data}}"/>

Compile and run. What's the magic. The control displays one item in our case it will be string, 'cos we have no template. Let's do it.

<DataTemplate DataType="{x:Type l:RedBlackItem}" x:Key="OtherTemplate">
    <Ellipse Width="100" Height="100">
            <Ellipse.Style>
                <Style>
                    <Setter Property="Ellipse.Fill" Value="Black"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=IsRed}" Value="True">
                            <Setter Property="Ellipse.Fill" Value="Red"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>
</DataTemplate>

Fair enough. Simple data template, that uses ellipse with color fill, according the property of the item. Now, all we have to do is connect the template and the control. ContentTemplate property of ContentControl looks very good candidate for it.

<ContentControl ContentTemplate="{StaticResource OtherTemplate}" Content="{Binding Source={StaticResource data}}"/>

Now, let's make another data template (implicit in this case) to show our items in ListBox. We'll use DataTriggers to defer the color of each item.

<DataTemplate DataType="{x:Type l:RedBlackItem}">
            <Border BorderBrush="Blue" BorderThickness="1">
                <Border.Resources>
                    <Style TargetType="Rectangle">
                        <Setter Property="Fill" Value="Black"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Path=IsRed}" Value="True">
                                <Setter Property="Fill" Value="Red"/>
                            </DataTrigger>
                         </Style.Triggers>
                    </Style>
                </Border.Resources>
                <Rectangle Width="100" Height="30"/>
            </Border>
        </DataTemplate>

Another thing we want to do is to detect selected item and put another filler for it. Smile in our case. Once, the items displayed by ListBoxItem, which is, actually ancestor of  our shape and have IsSelected dependency property, we can use relative source in DataTrigger to understand whether it selected or not. So fixing code like this will do the work

<DataTemplate DataType="{x:Type l:RedBlackItem}">
            <Border BorderBrush="Blue" BorderThickness="1">
                <Border.Resources>
                    <Style TargetType="Rectangle">
                        <Setter Property="Fill" Value="Black"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Path=IsRed}" Value="True">
                                <Setter Property="Fill" Value="Red"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}" Value="True">
                                <Setter Property="Fill">
                                    <Setter.Value>
                                        <VisualBrush>
                                            <VisualBrush.Visual>
                                                <Path Fill="Black" Stroke="Yellow" StrokeThickness="1">
                                                    <Path.Data>
                                                        <GeometryGroup>
                                                            <EllipseGeometry Center="50,15" RadiusY="10" RadiusX="10"/>
                                                            <EllipseGeometry Center="45,13" RadiusY="3" RadiusX="3"/>
                                                            <EllipseGeometry Center="55,13" RadiusY="3" RadiusX="3"/>
                                                            <LineGeometry StartPoint="50,15" EndPoint="50,20"/>
                                                        </GeometryGroup>
                                                    </Path.Data>
                                                </Path>
                                            </VisualBrush.Visual>
                                        </VisualBrush>
                                    </Setter.Value>
                                </Setter>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Border.Resources>
                <Rectangle Width="100" Height="30"/>
            </Border>
        </DataTemplate>

Now, when we can select items inside ItemsControl, we want to take it a bit far and select it by using control buttons. Let's make two RoutedCommand for this purpose and bind it by using command binding to our Window.

public static RoutedCommand MoveToPreviousCommand;
public static RoutedCommand MoveToNextCommand;

static Window1()
        {
            MoveToPreviousCommand = new RoutedCommand("MoveToPrevious", typeof(Window1));
            MoveToNextCommand = new RoutedCommand("MoveToNext", typeof(Window1));

            CommandManager.RegisterClassCommandBinding(typeof(Window1), new CommandBinding(MoveToPreviousCommand, ExecutedMoveCommand));
            CommandManager.RegisterClassCommandBinding(typeof(Window1), new CommandBinding(MoveToNextCommand, ExecutedMoveCommand));

        }

Inside the handler, we'll check what to do and use MoveToPreviouse/MoveToNext methods of collection view to manage it. Also, we want our collection to select first member, when we reach the end of it and last member, when we want one item before the first one. We might have all necessary information inside CollectionView to do it

static void ExecutedMoveCommand(object s, ExecutedRoutedEventArgs e)
        {
            Window1 w = s as Window1;
            if (e.Command == MoveToPreviousCommand)
            {
                w.data.View.MoveCurrentToPrevious();
                if (w.data.View.IsCurrentBeforeFirst)
                {
                    w.data.View.MoveCurrentToLast();
                }
            }
            else if (e.Command == MoveToNextCommand)
            {
                w.data.View.MoveCurrentToNext();
                if (w.data.View.IsCurrentAfterLast)
                {
                    w.data.View.MoveCurrentToFirst();
                }
            }
        }

All you have to do now is to create two RepeatButtons with nice vector arrows for back and forward to execute two of our commands and manage the collection

<RepeatButton Command="{x:Static l:Window1.MoveToPreviousCommand}"><Path Data="M1,1 L2,2 2,0 1,1"/></RepeatButton>
<RepeatButton Command="{x:Static l:Window1.MoveToNextCommand}"><Path Data="M2,1 L1,2 1,0 2,1"/></RepeatButton>

We done. A little facelifting and it's ready. Now we can know what item is selected by differing it inside it's visual presentation and data behind and viewing it as current item independently.

Thank you WPF for easy life. Be good people and learn a lot in Mix today.

Stay tuned for next days to my blog, In spite of the fact, that I'm not in Mix this year, I'm going to write about new beta version of Silverlight 2.0 with non-commercial go-life licence, small talk about XNA and my TechEd presentation, a bit about mobile development and other very cool things. Be sure to subscribe to my RSS feed for all this information.

Source code for this article

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/yet-other-large-industrial-area-switched-to-vista/]


I do not think, it's necessary to explain that. Those photos took in large industrial area in center region of Israel. A picture worth a hundred words...

IMAG0009

... and a little zoom

IMAG0010

Long life, Microsoft Windows Vista...

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/quick-tip-how-to-open-popup-or-contextmenu-in-xbap-application/]


If you want to use ContextMenu in XBAP application this will work, only if your XBAP is in full trust mode. Else you'll get "Request for the permission of type 'System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed". But even with Full Trust application, if you'll try to open context menu explicitly it wont. The error, you'll get will be "Cannot create a top-level child window". This is right. You cannot use layered windows without explicitely set parent inside browser sandbox.

Why this happens? Honestly, as for me, this is bug inside System.Windows.Controls.PopupControlService internal class (tnx to Shared .NET source code). For some reason PopupControlService set parent of the context menu while it raises ToolTipOpenning event. More, then this, it uses internal dependency property OwnerProperty to do it. So pity.

_currentToolTip.SetValue(OwnerProperty, o);

And if it is not enough it probably has memory leak, I described earlier.

_currentToolTip.Closed += OnToolTipClosed;
private void OnToolTipClosed(object sender, EventArgs e)
        {
            ToolTip toolTip = (ToolTip)sender;
            toolTip.Closed -= OnToolTipClosed;

Never mind. The question is how to take care on it, when we have no public Owner property and Parent is read only? Fortunately, dev team leave us PlacementTarget property to explicitly set the owner of ContextMenu. So, all you have to do is to add one line before setting IsOpen property to true.

private void OnMouseDown(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                contextMenu.PlacementTarget = sender as UIElement;
                contextMenu.IsOpen = true;
            }
        }

 

That's all, folks. Now you can safely open ContextMenu in XBAP application not only with right, but also with Left mouse button as well as with keyboard event or any other code you want to.

Have a nice day.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/how-to-high-performance-graphics-in-wpf/]


Microsoft DPE: "WPF is ever best super performance oriented technology for creating hyper multi point graphs, using parallelism and huge in-memory persistence vector scrounged math distributed calculations... And ever more with branded new Microsoft Windows Vista 7.

Client: Errr, well.... Let's try to apply it for our VB program...

DPE: You can easily do it yourself, but it'd be better to call someone from Microsoft Consulting Services.

Client: Let it be...

MCS: Well. It's too huge for WPF to scale... WPF uses a retained rendering system. It saves every little pixel and make you able scale and repaint very often without the composition system blocking on callbacks to your code. However, 1,000,000 retained pixels is too huge to scale...

Client: I want it scale. They promised... They told, it'll scale. Make it to do what I want it to do!!!

MCS: Errr, well. Let it be!

This is very common dialog between DPE, MCS and clients. Sales men want it to do, what he need it to do. Client want it to do what sales men promised to do and Services men should make it to do what they both want it to do. Today we'll speak about retained, lazy and loose models to produce large scale graphics.

First problem: multithreading

Even before we start to work, you should know, that we cannot create dependency objects in thread other, then current UI thread. We can use locks, mutexes, semaphores, however we still can not create Dependency Objects in other thread. In order to get rid of it, we'll have to use INofityPropertyChanged implementation, instead of Dependency Objects. This means, no Dependency Properties.

So, we'll start with following code (I'll reuse nice code written by Luis Diego Fallas to create Mandelbrot Fractal set)

class FractsCollection : INotifyPropertyChanged
    {

Second problem: rendering thread

Well, the problem is knows. There is only one UI thread. We wont it be only one, so we'll use our own HostVisual by Dwayne Need to enhance the performance.

Third problem: Retained objects

Actually, this is not problem. This is feature. And it can be extremely useful if you want to retain layout. Let's create a simple example: Dragon curve fractal. It has limited number of points, generated by well known final algorithm. So, we'll create our own geometry, derived from Shape class. The fasted geometry is StreamGeometry, so let's use it. First of all let's create the class and save the array of points.

public class DragonShape:Shape
    {
StreamGeometry dragonGeometry;
        double _angle;
        List<Point> Points;

Then we'll generate the pattern

void GeneratePattern()
        {
            ThreadPool.QueueUserWorkItem(delegate
            {
Move(5);
Turn(GetNextPoint * System.Math.PI / 180.0);

Then, by overriding DefiningGeometry property, create the fractal

protected override System.Windows.Media.Geometry DefiningGeometry
        {
            get { 
                using (StreamGeometryContext context = dragonGeometry.Open())
                {
                    context.BeginFigure(Points[0], false, false);
                    context.PolyLineTo(Points, true, false);
                }
                return (Geometry)dragonGeometry.GetAsFrozen();}
        }

Don't forget to tell the shape, that geometry was changed

this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, (SendOrPostCallback)delegate
                    {
                        this.InvalidateVisual();
                    }, null);

Now, run it and we'll have very nice vector fractal generated that can be easily resized and scaled. Here the result.

image image

This method will work fine for 1,000, even 10,000 points. But after a while you'll experience performance degradation. What to do? The client wants 10,000,000 (!) points (and in Winforms GDI+ it works for him)

Let's try to understand why. Because it is not retain. It's image! So, let's use image to make the play fair.

The fastest BitmapSource is InteropBitmap. It has an ability to update itself from the memory section. That's exactly what we'll use

format = PixelFormats.Bgr32;
            max = format.BitsPerPixel;
            uint count = (uint)(sWidth * sHeight * format.BitsPerPixel / 8);
            section = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, 0x04, 0, count, null);
            map = MapViewOfFile(section, 0xF001F, 0, 0, count);
            pixels = new byte[count];
            Marshal.Copy(pixels, 0, map, (int)count);
            source = System.Windows.Interop.Imaging.CreateBitmapSourceFromMemorySection(section, (int)sWidth, (int)sHeight, format, (int)(sWidth * format.BitsPerPixel / 8), 0) as InteropBitmap;
            ThreadPool.QueueUserWorkItem(delegate
            {
                while (true)
                {
                    Generate();
                }
            });

To get the source to bind to we'll get frozen image. Call Invalidate first to reread the source.

InteropBitmap source;
        public BitmapSource Source
        {
            get
            {
                source.Invalidate();
                return (BitmapSource)source.GetAsFrozen();
            }
        }

Now, when we ready to display we can just put pixels simultaneously (by using Parallel extension and PLINQ) and tell the WPF that our count and ImageSource property updated upon each pixel.

unsafe
            {
                uint* pBuffer = (uint*)map;
                Parallel.For(0, (int)sHeight, delegate(int yi)
                {
                    foreach (var p in from xi in Enumerable.Range(0, (int)sWidth).AsParallel()
                                      let mappedX = xF(xi)
                                      let mappedY = yF(yi)
                                      let p0 = new TranslatePoint(xF(xi), yF(yi))
                                      let function = constructor(p0)
                                      select new
                                      {
                                          x = xi,
                                          y = yi,
                                          xD = mappedX,
                                          yD = mappedY,
                                          i = apply(function, p0)
                                                  .TakeWhile(
                                                     (x, j) => j < max && x.NormSquared() < 4.0)
                                                  .Count()
                                      })
                    {
                        pBuffer[(int)(p.x + p.y * sWidth)] = (uint)(uint)((uint)0xFF << 24) |
                             (uint)(p.i << 16) |
                             (uint)(5*p.i << 8) |
                             (uint)(15*p.i); ;
                        count++;
                        FireUpdate();
                    }
                });
            }

void FireUpdate()
        {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("PixelCount"));
                    PropertyChanged(this, new PropertyChangedEventArgs("Source"));
                }
        }

We done. Now let's see how fast it can be to generate and display 10,000,000 live pixels (it's about 16,000x16,00x32bit image) in screen. The maximum, I was able to get with my Dell Latitude D820 was 1,200,000 x 1,200,000 pixels indexed 4 bit image (it's about 100,000,000 points) and my memory is over :)

Not bad, ah? So WPF scales and DPE are right? Not quite right, but let them to do their work and we'll be behind to come all client's dreams (and DPE's promises) true.

Have a nice day and be good people. Now you can use WPF for drawing big number of points.

More Posts « Previous page