Thursday, January 26, 2012

DataBinding to a WPF grid's cell

The idea was to use the data binding capabilities of WPF to ensure that a given element placed inside a grid's cell stays square (i.e. width = height). The problem is that it is not possible to directly access the height of a given row... Thus the trick is to place a border (zero thickness = not visible) inside that row and bind the element that should stay square to it.

For example like that:


<Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height="100*"/>
            <RowDefinition Height="10*" />
            <RowDefinition Height="30*" />
        Grid.RowDefinitions>
        <Grid Grid.Row="0" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100*" />
                <ColumnDefinition Width="10*" />
            Grid.ColumnDefinitions>
            <Border Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="MyBorder" />
            <WindowsFormsHost Grid.Column="0" Grid.Row="0" Width="{Binding ElementName=MyBorder, Path=ActualHeight}" Name="host" VerticalAlignment="Stretch" Loaded="host_Loaded"/>
            <my:ColorScaleViewer Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="colorScaleViewer1"/>
        Grid>
Grid>

Wednesday, January 25, 2012

OnIdle Event in WPF

The OnIdle Event is fired as soon as the computer does not have any tasks to execute. Thus it is a very convenient way to decide when refreshing graphical components. Assuming you have a program that streams a bunch of data from a given source, each time a couple of data arrive you trigger the refresh display function along with your processing functions. What will appends is that the graphical elements refreshing is pretty slow, thus the data start to accumulate in the buffer and the time lag between the data live stream and the graphical result on the screen increases.
The solution to this issue is to process the data when they arrive, but not do any refreshing on the screen nor the graphical components. The screen will be refreshed only when the computer has time, which will be the case when the event OnIdle fires.

Use the following code to subscribe to  the OnIdle Event:


using System.Windows.Interop;
       
private void Window_Loaded(object sender, RoutedEventArgs e)
{    
ComponentDispatcher.ThreadIdle += new EventHandler(OnIdle);
}

The method where to put the refreshing functions:


private void OnIdle(object sender, EventArgs e)
{
// Refreshing display functions
}

Notice that this way of processing the data and refreshing the screen, prioritize the processing (possible also the recording) of the data compared to displaying the data to the user. The human eye can only see 25 images per second anyway, so it does not make sense in most of the case to refresh each time a data arrives.

Cross-thread operation not valid in WPF


Elegant solution to commonly encountered issue:

this.Dispatcher.Invoke((Action)(() =>
            {
                //add your cross-threads operations here for example
                //richTextBoxOut.AppendText(text);
            }));

WPF Colors Scale

I was trying to implement a color scale which should be place beside a graph. The first idea was to use a 64 by 1 grid and fill each grid's cell with an appropriate color. But the result looked quite unexpected:


Of course I did set all margin, border and stroke size to zero, but the problem seems to come from the rounding to the pixel grid of the screen of each cell's size compared with the container's size.

Thus the next idea was to draw rectangle and here again the same problem raised. So I figured out that one needed a completely new approach:


The basic idea is to generate a bitmap image on the fly which is 1 pixel wide and n pixels in height where n also corresponds to the number of colors of the color scale. Then one can scale the bitmap to fill the container:






Here is the complete code:

        public void CompleteColorScale()
        {
            int Size = 64;
            double RectHeight = (int)(DrawPlane.ActualHeight / Size);
            double RectWidth = (int)DrawPlane.ActualWidth;

            int width=1;
            int height=Size;

             WriteableBitmap bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
               
            int red;
            int green;
            int blue;
            int alpha;

            uint[] pixels = new uint[width * height];

            for (int i = 0; i < Size; i++)
            {
                red = 0;
                green = 255 * i / height;
                blue = 255 ;
                alpha = 255;
                pixels[i] = (uint)((blue << 24) + (green << 16) + (red << 8) + alpha);
            }
            bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, width * 4, 0);
            DrawPlane.Source = bitmap;
            DrawPlane.Stretch = Stretch.Fill;
        }




XAML:
       
<Grid Name="ColorScale" Loaded="ColorScale_Loaded">
        <Image Name="DrawPlane">
           
        Image>
    Grid>