WPF

WPF : New Threading Component

As many of you have know I write quite a lot about UI work and WPF technology, and I have in the past written articles about Threading and have also written a great lot about my own WPF MVVM Framework Cinch which includes a lot of stuff to get you up and running doing WPF the MVVM way.

Well one thing that has always bugged me while working with WPF is threading and doing something in the background and keeping my UI nice and free to do other stuff. Sure I can spawn a new Thread or use the ThreadPool and even use the funky BackgroundTaskManager class inside of Cinch which does help a lot in terms of fetching data on an internally held BackgroundWorker and is completely unit testable, as discussed in this Cinch article. I just felt that more improvements could be made.

For example what I would want to be able to do, is have a ViewModel (to allow Binding and testing) that was being used as a DataContext for some View, to be able to accept some parametised work delegate that would be performed on a background thread, and would show some busy animation or status while the threading operation was happening, and would then either show an error status in the View somewhere if the background threading operation failed, or would show me the actual data that the background thread fetched that I requested in the parametised work delegate, if there was NO failure while fetching the relevant data.

I should point out that the code contained here in, does spawn a new BackgroundWorker per threading operation, where each operation is expected to return some single definable bit of data, such as a single List<SomeClass> or even some other very expensive time consuming data to fetch. My intent was NEVER to have some global threading manager that fetches all and sundry in 1 hit, its more micro managing the data.

In lamens terms, think of it like this (where "I" and "Me" in this italic paragraph means the demo app code provided in this article ) :

You want me to show a List<Contact> that could take some time to fetch, fair enough, I’ll create a BackgroundWorker to do that and manage that background activity, and let you know how its going, and when I am done I’ll return you a List<Contact>, or some error message. Oh and you also want me to show a List<BankAccount> on the same View that will also take some time to fetch, well now, for that I will need to return another type of List. In fact it will be a list of List<BankAccount> so I am going to be needing another BackgroundWorker to do that, and return you your list of List<BankAccount>.

I know this could potentially spawn a few threads, but internally the BackgroundWorker makes use of the ThreadPool which is ace, so I do not feel it’s an issue, as the management of Threads is done for us by the .NET Framework. Obviously if you have 100nds of lists of data on one View this article may not be for you at all, and you should stop reading right here. If on the other hand you have a few lists of data or a few expensive bits of data to fetch this code could well be for you.

Anyway that is how I see it working, and after messing around for a while I think I have done just that.

adorners

There are a few assumptions that I have made, which are as follows:

  • That people are using WPF, and are pretty competent with it. This is NOT a beginner article at all
  • That people are using the MVVM pattern
  • That people think its a good idea to have parts of your View show error messages if the data that was supposed to be fetched failed to happen
  • That people are happy with the idea of a item of data (such as a List of data, or expensive bits of data, being fetched by a dedicated BackgroundWorker manager object, which we will get to later).

If you want to know more about how all this works you can read the full article and grab the source code from there:

http://www.codeproject.com/KB/WPF/ThreadingComponent.aspx

WPF

Better WPF Circular Progress Bar

A while back I posted a blog post about a simple Circular Progress Bar that I did for WPF. The original post is right here : http://sachabarber.net/?p=429

It turns out that was not the best thing to do, as the old approach used a never ending animation, that was even running when the controls Visibility changed. I did notice this pretty quickly, when we profiled our app, and noticed this hot spot exactly where the progress bar was. So what we did to fix that is just remove the control when it should stop showing progress. Anyway that was the old way.

I am pleased to announce that I have a new improved Circular Progress Bar that no longer uses a never ending animation, in fact it is a lot simpler and just uses a DispatcherTimer and some elementary trigonometry, and it actually looks more like the style of progress bar we are all used to seeing on the web. Without further ado here is the code:

The xaml for the CircularProgressBar.xaml

<UserControl x:Class="ThreadingComponent.CircularProgressBar"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="Auto" Width="Auto" Background="Transparent" 
             IsVisibleChanged="HandleVisibleChanged">
    <Grid x:Name="LayoutRoot" Background="Transparent" 
          ToolTip="Searching...."
          HorizontalAlignment="Center" 
          VerticalAlignment="Center">
        <Canvas RenderTransformOrigin="0.5,0.5" 
                HorizontalAlignment="Center" 
             VerticalAlignment="Center" Width="120" 
             Height="120" Loaded="HandleLoaded" 
                Unloaded="HandleUnloaded"  >
            <Ellipse x:Name="C0" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="1.0"/>
            <Ellipse x:Name="C1" Width="20" Height="20" 
                     Canvas.Left="0"
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.9"/>
            <Ellipse x:Name="C2" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.8"/>
            <Ellipse x:Name="C3" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.7"/>
            <Ellipse x:Name="C4" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.6"/>
            <Ellipse x:Name="C5" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.5"/>
            <Ellipse x:Name="C6" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.4"/>
            <Ellipse x:Name="C7" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.3"/>
            <Ellipse x:Name="C8" Width="20" Height="20" 
                     Canvas.Left="0" 
                     Canvas.Top="0" Stretch="Fill" 
                     Fill="Black" Opacity="0.2"/>
            <Canvas.RenderTransform>
                <RotateTransform x:Name="SpinnerRotate" 
                     Angle="0" />
            </Canvas.RenderTransform>
        </Canvas>
    </Grid>
</UserControl>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And here is the CircularProgressBar.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Input;
using System.Windows.Shapes;

namespace ThreadingComponent
{
    /// <summary>
    /// A circular type progress bar, that is simliar to popular web based
    /// progress bars
    /// </summary>
    public partial class CircularProgressBar
    {
        #region Data
        private readonly DispatcherTimer animationTimer;
        #endregion

        #region Constructor
        public CircularProgressBar()
        {
            InitializeComponent();

            animationTimer = new DispatcherTimer(
                DispatcherPriority.ContextIdle, Dispatcher);
            animationTimer.Interval = new TimeSpan(0, 0, 0, 0, 75);
        }
        #endregion

        #region Private Methods
        private void Start()
        {
            Mouse.OverrideCursor = Cursors.Wait;
            animationTimer.Tick += HandleAnimationTick;
            animationTimer.Start();
        }

        private void Stop()
        {
            animationTimer.Stop();
            Mouse.OverrideCursor = Cursors.Arrow;
            animationTimer.Tick -= HandleAnimationTick;
        }

        private void HandleAnimationTick(object sender, EventArgs e)
        {
            SpinnerRotate.Angle = (SpinnerRotate.Angle + 36) % 360;
        }

        private void HandleLoaded(object sender, RoutedEventArgs e)
        {
            const double offset = Math.PI;
            const double step = Math.PI * 2 / 10.0;

            SetPosition(C0, offset, 0.0, step);
            SetPosition(C1, offset, 1.0, step);
            SetPosition(C2, offset, 2.0, step);
            SetPosition(C3, offset, 3.0, step);
            SetPosition(C4, offset, 4.0, step);
            SetPosition(C5, offset, 5.0, step);
            SetPosition(C6, offset, 6.0, step);
            SetPosition(C7, offset, 7.0, step);
            SetPosition(C8, offset, 8.0, step);
        }


        private void SetPosition(Ellipse ellipse, double offset, 
            double posOffSet, double step)
        {
            ellipse.SetValue(Canvas.LeftProperty, 50.0 
                + Math.Sin(offset + posOffSet * step) * 50.0);

            ellipse.SetValue(Canvas.TopProperty, 50 
                + Math.Cos(offset + posOffSet * step) * 50.0);
        }


        private void HandleUnloaded(object sender, RoutedEventArgs e)
        {
            Stop();
        }

        private void HandleVisibleChanged(object sender, 
            DependencyPropertyChangedEventArgs e)
        {
            bool isVisible = (bool)e.NewValue;

            if (isVisible)
                Start();
            else
                Stop();
        }
        #endregion
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And to use it you can simply make it any size you like by putting it into a ViewBox like so:

<UserControl x:Class="ThreadingComponent.BusyUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ThreadingComponent"
    Height="Auto" Width="Auto" 
    HorizontalAlignment="Stretch" 
    VerticalAlignment="Stretch">

        
        <Viewbox Width="200" Height="200"
                HorizontalAlignment="Center" 
                VerticalAlignment="Center">
            <local:CircularProgressBar />
        </Viewbox>


    </Grid>

</UserControl>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And here is what it looks like when its running

All the code is here is a cut and pastable format, so no ZIP file this time, just cut and paste this code, if you don’t know how to do that, step away from the XAML.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

WPF

WPF : Simple 3D Charting

I have just published a new WPF article that demonstrates how to make a fairly simple 3D Chart using WPF and some of its 3D capabilities.

Its a 3D bar chart that allows uses to navigate through historical data. It has somewhat limited use, as it so closely matches my wifes business, business requirements, but could easily be adapted for someone else uses, and I do feel it is still a nice example of working with 3D and WPF.

This is what it looks like when it starts up:

chart1

And here is what is looks like when the user hover their mouse over a particular item within the chart.

chart2

It did take a little while to get right. And despite it somewhat limited use (as its tailored directly to suit my wifes requirements) there is still some rather cool 3D stuff you can learn by reading the articles text.

So if you appreciate the work I do, and the code that I publish, I sure would appreciate you taking the time to vote for this article.

The full articles description of how it all works, and the source code is available at :

http://www.codeproject.com/KB/WPF/Graph3D.aspx

Enjoy

MVVM, WPF

Cinch : More good news (again sorry)

I have just done another update to my Cinch MVVM Framework which I realise is probably a pain to those of you that are keeping up with the codebase.

I actually feel I am done with it for good now (well until the next time).

So what has changed this time. Well there are only 2 things:

1. Fixed NumericTextBoxBehavior so it now validates pasted contents to ensure its numeric

2. I replaced my own logging service with a more manly logging facade. Now I am a massive fan of Daniel Vaughans work, and he did an excellent job on his logging facade called Clog, But what I wanted for my Cinch MVVM Framework was something that would require hardly any changes. I was originally going to use Clog, but after a bit of a look, I decided to use 2 other WPF Disciples logging facade work called Simple Logging Facade (SLF).

Now this is not to say what Daniel did is bad, cos it sure aint, in fact SLF is really a subset of what Clog can do. Its just for Cinch MVVM Framework SLF seemed an easier (yes I am that lazy) fit.

So Cinch MVVM Framework now uses Simple Logging Facade (SLF), and makes use of the Log4Net facade, so the log entries use the Log4Net style, which can be configured through the App.Config.

Here is an example of the new App.Config required by my Cinch MVVM Framework

 

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <configSections>
   4:      <section name="log4net" 
   5:               type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
   6:      <section name="slf" 
   7:               type="Slf.Config.SlfConfigurationSection, slf"/>
   8:    </configSections>
   9:   
  10:    <slf>
  11:      <factories>
  12:        <!-- configure single log4net factory, 
  13:             which will get all logging output -->
  14:        <!-- Important: Set a reference to the log4net facade 
  15:             library to make sure it will be available at runtime -->
  16:        <factory type="SLF.Log4netFacade.Log4netLoggerFactory, 
  17:                 SLF.Log4netFacade"/>
  18:      </factories>
  19:    </slf>
  20:   
  21:   
  22:    <!-- configures log4net to write into a local file called "log.txt" -->
  23:    <log4net>
  24:      <!--  log4net uses the concept of 'appenders' to indicate where 
  25:            log messages are written to.
  26:            Appenders can be files, the console, databases, SMTP and much more
  27:      -->
  28:      <appender name="MainAppender" type="log4net.Appender.FileAppender">
  29:        <param name="File" value="log.txt" />
  30:        <param name="AppendToFile" value="true" />
  31:        <!--  log4net can optionally format the logged messages with a pattern. 
  32:              This pattern string details what information
  33:              is logged and the format it takes. 
  34:              A wide range of information can be logged, including message, 
  35:              thread, identity and more,
  36:              see the log4net documentation for details:
  37:              http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html
  38:        -->
  39:        <layout type="log4net.Layout.PatternLayout">
  40:          <conversionPattern value="%logger: %date [%thread] 
  41:                             %-5level - %message %newline" />
  42:        </layout>
  43:      </appender>
  44:      <root>
  45:        <level value="ALL" />
  46:        <appender-ref ref="MainAppender" />
  47:      </root>
  48:    </log4net>
  49:   
  50:  </configuration>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

These new features will be documented in the Cinch MVVM Framework documentation over at www.codeproject.com

 

Enjoy.

 

PS : I really am done with Cinch now I feel, those were the last changes I wanted to get in there.

C#, WPF

Friction Scrolling Now An WPF Attached Behaviour Too

A while ago I wrote about how to create a scrollable design surface in WPF, and how you could also add friction into the mix.

My original post was called “Creating A Scrollable Control Surface In WPF” which can be found at  the following url:

http://sachabarber.net/?p=225

This original blog post proved to be quite popular and one of my fellow WPF Disciples my homeboy Jeremiah Morrill took it upon himself to rewrite my little control to be a content control for Silverlight, which you can get to at  “Scrollable Friction Canvas For Silverlight” which can be found at  the following url:

http://sachabarber.net/?p=481

I have been asked for my original code a lot, and another of my friends, and founder of the WPF Disciples, Marlon Grech took my code and has further improved it for WPF users, by making it an attached behaviour so all you have to do is hook up one property on your ScrollViewer and bingo its a Friction enabled surface. Neato I say.

Here is Marlons attached behaviour code:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Input;
   8:  using System.Windows.Threading;
   9:   
  10:  namespace ScrollableArea
  11:  {
  12:      public class KineticBehaviour
  13:      {
  14:          #region Friction
  15:   
  16:          /// <summary>
  17:          /// Friction Attached Dependency Property
  18:          /// </summary>
  19:          public static readonly DependencyProperty FrictionProperty =
  20:              DependencyProperty.RegisterAttached("Friction", typeof(double), typeof(KineticBehaviour),
  21:                  new FrameworkPropertyMetadata((double)0.95));
  22:   
  23:          /// <summary>
  24:          /// Gets the Friction property.  This dependency property 
  25:          /// indicates ....
  26:          /// </summary>
  27:          public static double GetFriction(DependencyObject d)
  28:          {
  29:              return (double)d.GetValue(FrictionProperty);
  30:          }
  31:   
  32:          /// <summary>
  33:          /// Sets the Friction property.  This dependency property 
  34:          /// indicates ....
  35:          /// </summary>
  36:          public static void SetFriction(DependencyObject d, double value)
  37:          {
  38:              d.SetValue(FrictionProperty, value);
  39:          }
  40:   
  41:          #endregion
  42:   
  43:          #region ScrollStartPoint
  44:   
  45:          /// <summary>
  46:          /// ScrollStartPoint Attached Dependency Property
  47:          /// </summary>
  48:          private static readonly DependencyProperty ScrollStartPointProperty =
  49:              DependencyProperty.RegisterAttached("ScrollStartPoint", typeof(Point), typeof(KineticBehaviour),
  50:                  new FrameworkPropertyMetadata((Point)new Point()));
  51:   
  52:          /// <summary>
  53:          /// Gets the ScrollStartPoint property.  This dependency property 
  54:          /// indicates ....
  55:          /// </summary>
  56:          private static Point GetScrollStartPoint(DependencyObject d)
  57:          {
  58:              return (Point)d.GetValue(ScrollStartPointProperty);
  59:          }
  60:   
  61:          /// <summary>
  62:          /// Sets the ScrollStartPoint property.  This dependency property 
  63:          /// indicates ....
  64:          /// </summary>
  65:          private static void SetScrollStartPoint(DependencyObject d, Point value)
  66:          {
  67:              d.SetValue(ScrollStartPointProperty, value);
  68:          }
  69:   
  70:          #endregion
  71:   
  72:          #region ScrollStartOffset
  73:   
  74:          /// <summary>
  75:          /// ScrollStartOffset Attached Dependency Property
  76:          /// </summary>
  77:          private static readonly DependencyProperty ScrollStartOffsetProperty =
  78:              DependencyProperty.RegisterAttached("ScrollStartOffset", typeof(Point), typeof(KineticBehaviour),
  79:                  new FrameworkPropertyMetadata((Point)new Point()));
  80:   
  81:          /// <summary>
  82:          /// Gets the ScrollStartOffset property.  This dependency property 
  83:          /// indicates ....
  84:          /// </summary>
  85:          private static Point GetScrollStartOffset(DependencyObject d)
  86:          {
  87:              return (Point)d.GetValue(ScrollStartOffsetProperty);
  88:          }
  89:   
  90:          /// <summary>
  91:          /// Sets the ScrollStartOffset property.  This dependency property 
  92:          /// indicates ....
  93:          /// </summary>
  94:          private static void SetScrollStartOffset(DependencyObject d, Point value)
  95:          {
  96:              d.SetValue(ScrollStartOffsetProperty, value);
  97:          }
  98:   
  99:          #endregion
 100:   
 101:          #region InertiaProcessor
 102:   
 103:          /// <summary>
 104:          /// InertiaProcessor Attached Dependency Property
 105:          /// </summary>
 106:          private static readonly DependencyProperty InertiaProcessorProperty =
 107:              DependencyProperty.RegisterAttached("InertiaProcessor", typeof(InertiaHandler), typeof(KineticBehaviour),
 108:                  new FrameworkPropertyMetadata((InertiaHandler)null));
 109:   
 110:          /// <summary>
 111:          /// Gets the InertiaProcessor property.  This dependency property 
 112:          /// indicates ....
 113:          /// </summary>
 114:          private static InertiaHandler GetInertiaProcessor(DependencyObject d)
 115:          {
 116:              return (InertiaHandler)d.GetValue(InertiaProcessorProperty);
 117:          }
 118:   
 119:          /// <summary>
 120:          /// Sets the InertiaProcessor property.  This dependency property 
 121:          /// indicates ....
 122:          /// </summary>
 123:          private static void SetInertiaProcessor(DependencyObject d, InertiaHandler value)
 124:          {
 125:              d.SetValue(InertiaProcessorProperty, value);
 126:          }
 127:   
 128:          #endregion
 129:   
 130:          #region HandleKineticScrolling
 131:   
 132:          /// <summary>
 133:          /// HandleKineticScrolling Attached Dependency Property
 134:          /// </summary>
 135:          public static readonly DependencyProperty HandleKineticScrollingProperty =
 136:              DependencyProperty.RegisterAttached("HandleKineticScrolling", typeof(bool), 
 137:              typeof(KineticBehaviour),
 138:                  new FrameworkPropertyMetadata((bool)false,
 139:                      new PropertyChangedCallback(OnHandleKineticScrollingChanged)));
 140:   
 141:          /// <summary>
 142:          /// Gets the HandleKineticScrolling property.  This dependency property 
 143:          /// indicates ....
 144:          /// </summary>
 145:          public static bool GetHandleKineticScrolling(DependencyObject d)
 146:          {
 147:              return (bool)d.GetValue(HandleKineticScrollingProperty);
 148:          }
 149:   
 150:          /// <summary>
 151:          /// Sets the HandleKineticScrolling property.  This dependency property 
 152:          /// indicates ....
 153:          /// </summary>
 154:          public static void SetHandleKineticScrolling(DependencyObject d, bool value)
 155:          {
 156:              d.SetValue(HandleKineticScrollingProperty, value);
 157:          }
 158:   
 159:          /// <summary>
 160:          /// Handles changes to the HandleKineticScrolling property.
 161:          /// </summary>
 162:          private static void OnHandleKineticScrollingChanged(DependencyObject d, 
 163:              DependencyPropertyChangedEventArgs e)
 164:          {
 165:              ScrollViewer scoller = d as ScrollViewer;
 166:              if ((bool)e.NewValue)
 167:              {
 168:                  scoller.MouseDown += OnMouseDown;
 169:                  scoller.MouseMove += OnMouseMove;
 170:                  scoller.MouseUp += OnMouseUp;
 171:                  SetInertiaProcessor(scoller, new InertiaHandler(scoller));
 172:              }
 173:              else
 174:              {
 175:                  scoller.MouseDown -= OnMouseDown;
 176:                  scoller.MouseMove -= OnMouseMove;
 177:                  scoller.MouseUp -= OnMouseUp;
 178:                  var inertia = GetInertiaProcessor(scoller);
 179:                  if (inertia != null)
 180:                      inertia.Dispose();
 181:              }
 182:              
 183:          }
 184:   
 185:          #endregion
 186:   
 187:          #region Mouse Events
 188:          private static void OnMouseDown(object sender, MouseButtonEventArgs e)
 189:          {
 190:              var scrollViewer = (ScrollViewer)sender;
 191:              if (scrollViewer.IsMouseOver)
 192:              {
 193:                  // Save starting point, used later when determining how much to scroll.
 194:                  SetScrollStartPoint(scrollViewer, e.GetPosition(scrollViewer));
 195:                  SetScrollStartOffset(scrollViewer, new 
 196:                      Point(scrollViewer.HorizontalOffset, scrollViewer.VerticalOffset));
 197:                  scrollViewer.CaptureMouse();
 198:              }
 199:          }
 200:   
 201:   
 202:          private static void OnMouseMove(object sender, MouseEventArgs e)
 203:          {
 204:              var scrollViewer = (ScrollViewer)sender;
 205:              if (scrollViewer.IsMouseCaptured)
 206:              {
 207:                  Point currentPoint = e.GetPosition(scrollViewer);
 208:   
 209:                  var scrollStartPoint = GetScrollStartPoint(scrollViewer);
 210:                  // Determine the new amount to scroll.
 211:                  Point delta = new Point(scrollStartPoint.X - currentPoint.X, 
 212:                      scrollStartPoint.Y - currentPoint.Y);
 213:   
 214:                  var scrollStartOffset = GetScrollStartOffset(scrollViewer);
 215:                  Point scrollTarget = new Point(scrollStartOffset.X + delta.X, 
 216:                      scrollStartOffset.Y + delta.Y);
 217:   
 218:                  var inertiaProcessor = GetInertiaProcessor(scrollViewer);
 219:                  if (inertiaProcessor != null)
 220:                      inertiaProcessor.ScrollTarget = scrollTarget;
 221:                  
 222:                  // Scroll to the new position.
 223:                  scrollViewer.ScrollToHorizontalOffset(scrollTarget.X);
 224:                  scrollViewer.ScrollToVerticalOffset(scrollTarget.Y);
 225:              }
 226:          }
 227:   
 228:          private static void OnMouseUp(object sender, MouseButtonEventArgs e)
 229:          {
 230:              var scrollViewer = (ScrollViewer)sender;
 231:              if (scrollViewer.IsMouseCaptured)
 232:              {
 233:                  scrollViewer.ReleaseMouseCapture();
 234:              }
 235:          }
 236:          #endregion
 237:   
 238:          #region Inertia Stuff
 239:   
 240:          /// <summary>
 241:          /// Handles the inertia 
 242:          /// </summary>
 243:          class InertiaHandler : IDisposable
 244:          {
 245:              private Point previousPoint;
 246:              private Vector velocity;
 247:              ScrollViewer scroller;
 248:              DispatcherTimer animationTimer;
 249:   
 250:              private Point scrollTarget;
 251:              public Point ScrollTarget { 
 252:                  get { return scrollTarget; } 
 253:                  set { scrollTarget = value; } }
 254:   
 255:              public InertiaHandler(ScrollViewer scroller)
 256:              {
 257:                  this.scroller = scroller;
 258:                  animationTimer = new DispatcherTimer();
 259:                  animationTimer.Interval = new TimeSpan(0, 0, 0, 0, 20);
 260:                  animationTimer.Tick += new EventHandler(HandleWorldTimerTick);
 261:                  animationTimer.Start();
 262:              }
 263:   
 264:              private void HandleWorldTimerTick(object sender, EventArgs e)
 265:              {
 266:                  if (scroller.IsMouseCaptured)
 267:                  {
 268:                      Point currentPoint = Mouse.GetPosition(scroller);
 269:                      velocity = previousPoint - currentPoint;
 270:                      previousPoint = currentPoint;
 271:                  }
 272:                  else
 273:                  {
 274:                      if (velocity.Length > 1)
 275:                      {
 276:                          scroller.ScrollToHorizontalOffset(ScrollTarget.X);
 277:                          scroller.ScrollToVerticalOffset(ScrollTarget.Y);
 278:                          scrollTarget.X += velocity.X;
 279:                          scrollTarget.Y += velocity.Y;
 280:                          velocity *= KineticBehaviour.GetFriction(scroller);
 281:                      }
 282:                  }
 283:              }
 284:   
 285:              #region IDisposable Members
 286:   
 287:              public void Dispose()
 288:              {
 289:                  animationTimer.Stop();
 290:              }
 291:   
 292:              #endregion
 293:          }
 294:   
 295:          #endregion
 296:      }
 297:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Which to use you would simply do this :

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

   1:  <Window x:Class="ScrollableArea.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:local="clr-namespace:ScrollableArea"
   5:      Title="Window1" Height="300" Width="300">
   6:      <Window.Resources>
   7:   
   8:          <!-- scroll viewer -->
   9:          <Style x:Key="ScrollViewerStyle"
  10:                 TargetType="{x:Type ScrollViewer}">
  11:              <Setter Property="HorizontalScrollBarVisibility" 
  12:                      Value="Hidden" />
  13:              <Setter Property="VerticalScrollBarVisibility" 
  14:                      Value="Hidden" />
  15:          </Style>
  16:   
  17:      </Window.Resources>
  18:   
  19:      <Grid Margin="0">
  20:          <ScrollViewer x:Name="ScrollViewer" 
  21:              Style="{StaticResource ScrollViewerStyle}" 
  22:              local:KineticBehaviour.HandleKineticScrolling="True">
  23:              <ItemsControl x:Name="itemsControl" 
  24:                            VerticalAlignment="Center">
  25:   
  26:                  <ItemsControl.ItemsPanel>
  27:                      <ItemsPanelTemplate>
  28:                          <!-- Custom Panel-->
  29:                          <StackPanel Orientation="Vertical"/>
  30:                      </ItemsPanelTemplate>
  31:                  </ItemsControl.ItemsPanel>
  32:   
  33:   
  34:              </ItemsControl>
  35:          </ScrollViewer>
  36:      </Grid>
  37:   
  38:  </Window>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

As always here is a small demo app:

http://dl.dropbox.com/u/2600965/ScrollableAreaAttachedBehaviour.zip

MVVM, WPF

Cinch : Major change alert

I have been quite busy with a few other ideas and research lately, but there has always been one thing that has bothered me about my Cinch MVVM Framework which was how the business rules were declared on the business objects.

Previously the rules were instance based, and due to the added complications of me using a specialized Decorater pattern for some of the core business object properties (which is cool as it allow the VM to dictate the editability of the data) I have been struggling to see how to fix this for a while now.

The other day I had a chat with my mate at work a extremely smart chap by the name of Fredrik Bornander who I admire a lot (he is way smarter than I will ever be), and he helped me come up with something cool.

So now what we have is the best of both worlds we declare the rule instances statically per Type, but we add the static rule declarations to instance based fields to validate against.

Here is what we do now:

using System;

using Cinch;
using MVVM.DataAccess;
using System.ComponentModel;
using System.Collections.Generic;

namespace MVVM.Models
{


    public class OrderModel : Cinch.EditableValidatingObject
    {
        #region Data
        private Cinch.DataWrapper<Int32> quantity;

        //rules
        private static SimpleRule quantityRule;

        #endregion

        #region Ctor
        public OrderModel()
        {
            #region Create DataWrappers

            Quantity = new DataWrapper<Int32>(this, quantityChangeArgs);


            #endregion

            #region Create Validation Rules

            quantity.AddRule(quantityRule);

            #endregion

        }

        static OrderModel()
        {
            quantityRule = new SimpleRule("DataValue", 
                 "Quantity can not be < 0",
                      (Object domainObject)=>
                      {
                          DataWrapper<Int32> obj = 
                           (DataWrapper<Int32>)domainObject;
                          return obj.DataValue <= 0;
                      });
        }


        #endregion

        #region Public Properties


        /// <summary>
        /// Quantity
        /// </summary>
        static PropertyChangedEventArgs quantityChangeArgs =
            ObservableHelper.CreateArgs<OrderModel>(x => x.Quantity);

        public Cinch.DataWrapper<Int32> Quantity
        {
            get { return quantity; }
            private set
            {
                quantity = value;
                NotifyPropertyChanged(quantityChangeArgs);
            }
        }



        #endregion

       
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I am most pleased with this now, as it will sure decrease the amount of memory required by each business object, but it still allows the instance fields to be validated. Nice.

I will be updating the codebase and relevant articles that cover Cinch MVVM Framework some time today.

Thanks Fredrik, you crazy Swede. You rock

MVVM, WPF

WPF : Some Good Cinch News

I have now included some code snippets (C# only sorry) to help you develop ViewModels using my Cinch MVVM Framework.

I did not write these code snippets myself, one of the Cinch MVVM Framework users, a chap called Manuvdp uploaded them as a patch to the Cinch MVVM Framework codeplex site.

I have checked that I am happy with them before I included them, and I am. So what I have done is included Manuvdp original code snippets and also created a VSI Installer for them.

So after you have installed the snippets using the VSI, and you are doing a Cinch MVVM Framework app and type in “Cinch”, here is what you get:

snips

Neato.

In case any of you are wondering how to create a VSI installer for the code snippets here is a step by step guide.

Step1

Store your snippets in a folder somewhere (I called this CodeSnippets)

Step2

Create a new XML File in Visual Studio in the same folder as all your code snippets. I name this file CinchSnippets.xml. This xml file will be the manifest file.

Step3

Rename CinchSnippets.xml to CinchSnippets.vscontent

Step4

I edited the CinchSnippets.vscontent to include each of the snippets I wanted to be part of the VSI installer. Shown below is an example of what this xxxxx.vscontent manifest file should look like:

<?xml version="1.0" encoding="UTF-8"?>
<VSContent
  xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
  <Content>
    <FileName>Cinch0.snippet</FileName>
    <DisplayName>Cinch : Code snippet for a 
    EditableValidatingObject overrides</DisplayName>
    <Description>Code snippet for a 
    EditableValidatingObject overrides
    </Description>
    <FileContentType>Code Snippet</FileContentType>
    <ContentVersion>1.0</ContentVersion>
    <Attributes>
      <Attribute name="lang" value="csharp"></Attribute>
    </Attributes>
  </Content>
  <Content>
    <FileName>CinchBGWorker.snippet</FileName>
    <DisplayName>Cinch : Code snippet for a 
    Background worker</DisplayName>
    <Description>
      Code snippet for a Background worker
    </Description>
    <FileContentType>Code Snippet</FileContentType>
    <ContentVersion>1.0</ContentVersion>
    <Attributes>
      <Attribute name="lang" value="csharp"></Attribute>
    </Attributes>
  </Content>
</VSContent>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This is for 2 snippets, which you can see in the Content elements. If you need more, just add more Content elements.

Step5

I then zipped all the xxxx.snippet files and the xxxxx.vscontent file (CinchSnippets.vscontent in my case) into a new zip file, say CinchCodeSnippets.zip

Step6

I then renamed the zip file CinchCodeSnippets.zip –> CinchCodeSnippets.vsi

Step7

You can then run  the VSI installer by double clicking on the newly created CinchCodeSnippets.vsi

 

NOTE :I Would not download the entire Cinch MVVM Framework again, as I am still working on one improvement which I hope to have resolved very soon. You should however be able to grab all the items new CodeSnippets folder, and I will make an announcement on this blog when I have made the improvement or not. Depends on how hairy it gets, either way I will let you all know.

Enjoy

WPF

WPF : RatingsControl That Supports Fractions

I was at work the other day and one of my work collegues asked me how to create a Rating control (you know the ones with the stars). I talked him through how to do it, but whilst doing so I thought I might have a go at that if I get a spare hour or 2. I found some time to give it a go, and have come up with what I think is a pretty flexable RatingControl for WPF.

You are able to alter the following attributes

  • Overall background color
  • Star foreground color
  • Star outline color
  • Number of stars
  • Current value

All of these properties are DependencyProperty values, so they are fully bindable. This is all wrapped up in a very simple and easy to use UserControl called RatingsControl, Here is what the resulting RatingsControl looks like in a demo window.

Stars

The RatingsControl also contains n many StarControls that each renders its own value portion. You may be asking yourself how the control can render partial stars, well this figure may explain that

Stars2

Here is the code from the StarControl Value DP, which moves a masking Rectangle the correct distance to give the illusion of a fraction rendering of the Star. The masking Rectangle is clipped by using a standard Rectangle Clip value for the StarControl.

/// <summary>
/// Handles changes to the Value property.
/// </summary>
private static void OnValueChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
{
    d.CoerceValue(MinimumProperty);
    d.CoerceValue(MaximumProperty);
    StarControl starControl = (StarControl)d;
    if (starControl.Value == 0.0m)
    {
        starControl.starForeground.Fill = Brushes.Gray;
    }
    else
    {
        starControl.starForeground.Fill = starControl.StarForegroundColor;
    }

    Int32 marginLeftOffset = (Int32)(starControl.Value * (Decimal)STAR_SIZE);
    starControl.mask.Margin = new Thickness(marginLeftOffset, 0, 0, 0);
    starControl.InvalidateArrange();
    starControl.InvalidateMeasure();
    starControl.InvalidateVisual();

}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

If you want to know more and get the download code you can do so using the article link which is :

http://www.codeproject.com/KB/WPF/WPFStarRating.aspx

Enjoy

WPF

WPF : A Strange Layout Issue

The other day I was doing a new View for a WPF app that we are working on, that required a DataTemplate that consisted something like the following:

<DataTemplate DataType="{x:Type local:EmbeddedViewModel}">
    <Expander x:Name="exp" 
    Background="Transparent"
          IsExpanded="True" ExpandDirection="Down" 
          Expanded="Expander_Expanded" 
              Collapsed="Expander_Collapsed">


        <ListView AlternationCount="0" 
             Margin="0" 
             Background="Coral"
             ItemContainerStyle="{DynamicResource ListItemStyle}"
             BorderBrush="Transparent"
             VerticalAlignment="Stretch"
             HorizontalAlignment="Stretch"
             ItemsSource="{Binding SubItems}"
             IsSynchronizedWithCurrentItem="True"
             SelectionMode="Single">

            <ListView.View>
                <GridView>

                    <GridViewColumn  
                        Width="100" Header="FieldA"
                        DisplayMemberBinding="{Binding FieldA}"/>

                    <GridViewColumn  
                        Width="100" Header="FieldB"
                        DisplayMemberBinding="{Binding FieldB}"/>

                </GridView>

            </ListView.View>
        </ListView>
    </Expander>
</DataTemplate>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Which is all cool, but when I let the View out there into the wild for testing the users were like, yeah that’s ok when there are lots of items, but what happens when there is only 1 item. We would like that 1 item to take up all the available space of the screen. And it got me thinking into how to do this. So this is what I came up with.

A ItemsControl that has a specialized Grid (GridWithChildChangedNoitfication) control as its ItemsPanelTemplate. The specialized grid simply raises an event to signal that it has had a new VisualChild added.

Here is the code for the full ItemsControl setup

<ItemsControl x:Name="items" 
    ItemsSource="{Binding Path=Items, Mode=OneWay}" 
    Background="Transparent" 
    ScrollViewer.VerticalScrollBarVisibility="Auto">

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <local:GridWithChildChangedNoitfication 
            VerticalAlignment="Stretch" 
            HorizontalAlignment="Stretch"
            Margin="0" Loaded="ItemsGrid_Loaded"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type local:EmbeddedViewModel}">
            <Expander x:Name="exp" 
            Background="Transparent"
                  IsExpanded="True" ExpandDirection="Down" 
                  Expanded="Expander_Expanded" 
                      Collapsed="Expander_Collapsed">


                <ListView AlternationCount="0" 
                     Margin="0" 
                     Background="Coral"
                     ItemContainerStyle="{DynamicResource ListItemStyle}"
                     BorderBrush="Transparent"
                     VerticalAlignment="Stretch"
                     HorizontalAlignment="Stretch"
                     ItemsSource="{Binding SubItems}"
                     IsSynchronizedWithCurrentItem="True"
                     SelectionMode="Single">

                    <ListView.View>
                        <GridView>

                            <GridViewColumn  
                                Width="100" Header="FieldA"
                                DisplayMemberBinding="{Binding FieldA}"/>

                            <GridViewColumn  
                                Width="100" Header="FieldB"
                                DisplayMemberBinding="{Binding FieldB}"/>

                        </GridView>

                    </ListView.View>
                </ListView>
            </Expander>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And here is what the C# code looks like for the specialized Grid (GridWithChildChangedNoitfication).

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace ShareOfSize
{
    public class GridWithChildChangedNoitfication : Grid
    {

        /// <summary>
        /// Override that allows us to tell when the
        /// VisualChildren collection has changed
        /// </summary>
        protected override void OnVisualChildrenChanged(
            DependencyObject visualAdded, 
            System.Windows.DependencyObject visualRemoved)
        {
            base.OnVisualChildrenChanged(visualAdded, 
                visualRemoved);
            OnGridVisualChildrenChanged(new EventArgs());
        }


        /// <summary>
        /// Raise an event to signal that a VisualChild has been
        /// added/removed
        /// </summary>
        public event EventHandler<EventArgs> 
            GridVisualChildrenChanged;

        protected virtual void 
            OnGridVisualChildrenChanged(EventArgs e)
        {
            EventHandler<EventArgs> handlers = 
                GridVisualChildrenChanged;

            if (handlers != null)
            {
                handlers(this, e);
            }

        }
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

The last piece in the puzzle is some code behind that knows how to resize all the children in the ItemsControl when the VisualChild count of the specialized grid changes.

NOTE : This more than likely could be abstracted to a Attached Behaviour via an Attached Property, I’ll leave that as an activity for the user should they wish to do that. For me as it was so UI I did not mind this bit of code behind.

    public partial class Window1 : Window
    {

        private GridWithChildChangedNoitfication itemsGrid;


        public Window1()
        {
            InitializeComponent();
            this.DataContext = new Window1ViewModel();
            this.Unloaded += ViewUnloaded;

        }


        private void ViewUnloaded(object sender, RoutedEventArgs e)
        {
            if(itemsGrid != null)
            {
                itemsGrid.GridVisualChildrenChanged 
                    -= GridChildrenChanged;
            }
        }

        private void GridChildrenChanged(object sender, EventArgs e)
        {
            ResizeRows();
        }


        private void ResizeRows()
        {
            if (itemsGrid != null)
            {
                itemsGrid.RowDefinitions.Clear();
                for (int i = 0; i < itemsGrid.Children.Count; i++)
                {
                    RowDefinition row = new RowDefinition();
                    row.Height = new GridLength(
                        itemsGrid.ActualHeight/
                            itemsGrid.Children.Count,GridUnitType.Pixel);
                    itemsGrid.RowDefinitions.Add(row);
                            
                    itemsGrid.Children[i].SetValue(
                        HorizontalAlignmentProperty, 
                        HorizontalAlignment.Stretch);
                    itemsGrid.Children[i].SetValue(
                        VerticalAlignmentProperty, 
                        VerticalAlignment.Stretch);
                    itemsGrid.Children[i].SetValue(Grid.RowProperty,i);              
                }
            }         
        }



        private void ItemsGrid_Loaded(object sender, RoutedEventArgs e)
        {
            itemsGrid = sender as GridWithChildChangedNoitfication;
            if(itemsGrid != null)
            {
                itemsGrid.GridVisualChildrenChanged +=
                    GridChildrenChanged;
            }
        }

        private void Expander_Expanded(object sender, RoutedEventArgs e)
        {
            Expander expander = sender as Expander;
            var item = ItemsControl.ContainerFromElement(items, 
                (DependencyObject)sender);

            Int32 row = (Int32)(item).GetValue(Grid.RowProperty);

            if (expander.Tag != null)
            {
                itemsGrid.RowDefinitions[row].Height = 
                    new GridLength((Double)expander.Tag,GridUnitType.Pixel);
            }
        }

        private void Expander_Collapsed(object sender, RoutedEventArgs e)
        {
            Expander expander = sender as Expander;
            var item = ItemsControl.ContainerFromElement(items, 
                (DependencyObject)sender);

            Int32 row = (Int32)(item).GetValue(Grid.RowProperty);

            if (expander.Tag == null)
            {
                expander.Tag = itemsGrid.RowDefinitions[row].Height.Value;
            }
            itemsGrid.RowDefinitions[row].Height = 
                new GridLength(40,GridUnitType.Pixel);
        }



    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Here is what it all looks like

Share

Now I am sure there is some bright spark out there that could have done this with Grid.IsSharedSizeScope I just couldn’t see that myself, so this is what I came up with.

As always here is a small demo app:

http://sachabarber.net/wp-content/uploads/2009/11/ShareOfSize.zip

C#, WPF

Windows7 / VS2010 / WPF 4 Demo App

The other day I finished up a small demo app that I was writing over at www.codeproject.com which covers several of the new Windows7 features such as TaskBars/JumpLists.

The finished article looks like this

 

start2

start4

start5

task

The idea behind this demo app is actually very simple, I wanted to show how to use the Managed Extensibility Framework (MEF) to add in a bunch of Pixel Shaders that were inside a separate assembly. I also use some of the new .NET 4.0 goodies such as Dynamic and ExpandoObject.

The demo app also show cases how to use the new System.Windows.Shell namespace.

In case you are wondering here is how you would create a JumpList using the new System.Windows.Shell namespace.

JumpList jumpList = new JumpList();
JumpList.SetJumpList(Application.Current, jumpList);

JumpTask jumpTask = new JumpTask();
jumpTask.Title = "IE";
jumpTask.CustomCategory = 
    "Keep Notes";
jumpTask.ApplicationPath = 
    @"C:Program FilesInternet Exploreriexplore.exe";
String systemFolder = 
    Environment.GetFolderPath(
        Environment.SpecialFolder.System);
jumpTask.IconResourcePath = 
    @"C:Program FilesInternet Exploreriexplore.exe";
jumpTask.IconResourceIndex = 0;
jumpTask.Arguments = "pixel shaders";
jumpList.JumpItems.Add(jumpTask);
jumpList.Apply();

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And here is how you can create a TaskBar that can interact with your application code, again using the new System.Windows.Shell namespace.

<Window x:Class="MefFX.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MefFX" Height="600" Width="800">


    <Window.TaskbarItemInfo>
        <TaskbarItemInfo
            ProgressState="Normal" 
            Description="Some text"
            ThumbnailClipMargin=
        "{Binding RelativeSource=
            {RelativeSource FindAncestor, 
                    AncestorType={x:Type Window}}, 
            Path=BorderThickness}">
            <TaskbarItemInfo.ThumbButtonInfos>
                <ThumbButtonInfo
                   Click="SearchForPixelShadersInside_Click"
                    DismissWhenClicked="False"
                    ImageSource="Images/ie.png" />
                <ThumbButtonInfo Command="{Binding AboutCommand}"
                    DismissWhenClicked="False"
                    ImageSource="Images/about.png" />
            </TaskbarItemInfo.ThumbButtonInfos>
        </TaskbarItemInfo>


    </Window.TaskbarItemInfo>


....
....
....
....

</Window>

 

This TaskBar is obviously done in WPF4.0 using XAML but it would be easy enough to do in WinForms using the TaskbarItemInfo class which is in the new System.Windows.Shell namespace.

Anyway the full article explains all this and a lot more in a lot more detail, so if you feel inclined to have a read through of that, the full article is available over at this link:

http://www.codeproject.com/KB/WPF/MefFX.aspx

Enjoy
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }