Wednesday, April 11, 2007

Transparent WPF control over unmanaged window handle

All those, how even once tried to put opaque or transparent WPF control over Windows Forms control or, even unmanaged window found themselves within "Airspace problem". So, what to do? How can we use transparent WPF UI over our legacy windows? This how.

See the image above. As you can see, there is tarsparent XAML page (corners, plus and text) over legacy game window. How to do it? We'll put our transparent WPF window over Windows Forms window. First of all, we have to find the handle of our WPF application. We have only one for whole application, so let's get it

 

protected override void OnSourceInitialized(EventArgs e)
        {
            HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
 
            if (hwndSource != null)
            {
                handle = hwndSource.Handle;
                WindowFinder.Start(title, handle);
                WindowFinder.OnWindowFound += new WindowFinder.OnWindowFoundHandler(WindowFinder_OnWindowFound);                
            }
        }

So far, so good. Now we have to find the handle of our legacy window. In order to do it, we have to put ourselves into user32.dll and dig FindWindow method from there.


 



[DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

Now let's enter endless loop for finding our handle


 



IntPtr hWnd = FindWindow(null, (string)e.Argument);
            while (!w.CancellationPending && hWnd == m_ptr)
            {
                System.Threading.Thread.Sleep(100);
                hWnd = FindWindow(null, (string)e.Argument);
            }

Once got it by name, we have to measure it and then resize our WPF window to fit the new size and position. To do it, we'll use another method GetWindowRect


 



[DllImport("user32.dll")]
        static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
 

RECT r = new RECT();
            while (!w.CancellationPending && ((r.Left == r.Right) & (r.Bottom == r.Top)))
            {
                System.Threading.Thread.Sleep(100);
                GetWindowRect(m_ptr, out r);
            }
 
 
            m_rect = new Rect(r.Left,r.Top,r.Right-r.Left,r.Bottom-r.Top);

Got it and it's different then 0. Now let's resize the WPF window and put it over the legacy one and run from the beginning to track any change. Sure, the better way is to build global hook to legacy window messages and adjust properties of our WPF window. Do it. I do not really need it.


 



void WindowFinder_OnWindowFound(IntPtr handle, Rect position)
        {
            this.Left = position.Left;
            this.Top = position.Top;
            this.Width = position.Width;
            this.Height = position.Height;
            WindowFinder.Start(title, handle);
        }

We finished. Now we'll always have WPF window over legacy window, or control, or anything, that have name or handle. Even Windows Media Player window



A couple of things in the code attached not covered by article:



  • Half and MultipleNumber converters - those converters will help up to bind corners of WPF window into black Polylines, that you can see in the image. Those are really simple



    class HalfNumberConverter:IValueConverter
        {
            #region IValueConverter Members
     
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                double half = 0;
                if (double.TryParse(value.ToString(), out half))
                    half /= 2;
                double offset = 0;
                if(double.TryParse(parameter.ToString(), out offset))
                    half += offset;
     
                return half;
            }

    class MultipleNumberConverter:IValueConverter
        {
            #region IValueConverter Members
     
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                double val = 0;
                if (double.TryParse(value.ToString(), out val))
                {
                    char op = parameter.ToString()[0];
                    if (op == '*')
                        val *= double.Parse(parameter.ToString().Remove(0, 1));
                    else if (op == '/')
                        val /= double.Parse(parameter.ToString().Remove(0, 1));
                    if (op == '+')
                        val += double.Parse(parameter.ToString().Remove(0, 1));
                    else if (op == '-')
                        val -= double.Parse(parameter.ToString().Remove(0, 1));
                    else
                        val += double.Parse(parameter.ToString());
                }
                return val;
            }

     

  • StrokeTextBlock - this is hi-contrast textblock, that visible on any background. I wrote about how to build it earlier. Very useful control and any, especially this application

  • In case that you do not know - in order to move borderless transparent window you have to overwrite any OnMouse.... event and DragMove() the control there. The example of usage is OnMouseLeftButtonDown one.



    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
            {
                base.OnMouseLeftButtonDown(e);
                DragMove();
     
            }

  • Do you remember, that there is no hittest within transparent window, so you can use underlying window through our WPF form

  • To do you work, use BackgroundWorker - this save you actions related to thread context stuff.

I think, that most of things were covered by this, so see the source in attachment and when using it, don't forget to remember me.


Source code for this article

Tuesday, April 10, 2007

Preview image for MediaElement and more

This article covers two topics. One is how to get over ugly clr-namespace syntax by defining your own namespaces, the other - what is MediaPlayer control and how to create your own MediaElement clone with video preview as most of video hosting services like YouTube do. So let's start

First of all, I want my control looks like a normal one in XAML page. I do not want ugly clr-namespace:myNamespace etc, reference, I WANT URL!!!

<Window x:Class="MediaSense.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:l="http://schemas.sharpsoft.net/xaml"

    Title="MediaSense" Height="600" Width="800"

    >

    <l:PreviewMediaElement Name="video"  Width="640" Height="480" Source="../../Butterfly.wmv" />

</Window>

Don't "xmlns:l="http://schemas.sharpsoft.net/xaml" looks much better? How to do it. Just go to your AssemblyInfo.cs file in the assembly, where your control sits and add namespace mapping. Something like this, will be enough to make your reference looking better

 

[assembly: XmlnsDefinition("http://schemas.sharpsoft.net/xaml", "MediaSense")]

[assembly: XmlnsDefinition("http://schemas.sharpsoft.net/xaml", "MediaSense.Controls")]

Please, don't ask me why this does not works for controls in the assembly, that reference it. I do not know :). The other point is, that this method will not solve warnings like " The element 'StackPanel' in namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation' has invalid child element 'PreviewMediaElement' in namespace 'http://schemas.sharpsoft.net/xaml'. List of possible elements expected: 'StackPanel.CanHorizontallyScroll, etc., etc., etc.,"

The other thing that not covered by this, as well as the old method is intellisense and autocomplete. You can do it yourself by adding xsd schema file into C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas. You should create xsd like following in order to get over this problem and recieve intellisense. The only way to do it is by hand, due XSD Generator does not works good enought for XAML maps. Good forces are working on it those days, so it worth to wait

<?xml version="1.0" encoding="utf-8"?>

<xs:schema targetNamespace="http://schemas.sharpsoft.net/xaml"

  elementFormDefault="qualified"

  xmlns="http://schemas.sharpsoft.net/xaml"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  xmlns:xs="http://www.w3.org/2001/XMLSchema"

  >

  <xs:import namespace="http://schemas.sharpsoft.net/xaml" schemaLocation="xaml2006.xsd"/>

  <xs:element name="PreviewMediaElement" type="dPreviewMediaElement" substitutionGroup="xs:dMediaElement">

    <xs:annotation>

      <xs:documentation>Represents a control that contains audio and/or video with preview. </xs:documentation>

    </xs:annotation>

  </xs:element>

  <xs:complexType name="dPreviewMediaElement" >

    <xs:choice minOccurs="0" maxOccurs="unbounded">

      <xs:element name="MediaElement.Source" type="dUriContainer" minOccurs="0" maxOccurs="1">

        <xs:annotation>

          <xs:documentation>Gets or sets a media source on the MediaElement. This is a dependency property.</xs:documentation>

        </xs:annotation>

      </xs:element>

If you are using Orcas bit, you can forget all above, there even custom controls are having intellisese support as well as user controls in current framework 2.0

So far so good. Now, let's cover preview image. In order to draw first (or any other) video frame we have to treat video drawing "by code", so the only class usable for us is MediaPlayer.  After first initialization, we'll subscribe to Video Opened event to get the first frame drawn like this

 

void getFirstPreview()

        {

            RenderTargetBitmap target = new RenderTargetBitmap(_player.NaturalVideoWidth, _player.NaturalVideoHeight, 1 / 100, 1 / 100, PixelFormats.Pbgra32);

            DrawingVisual visual = new DrawingVisual();

            DrawingContext context = visual.RenderOpen();

            context.DrawVideo(_player, frameRect);

            context.Close();

            target.Render(visual);

 

            if (_prev == null)

            {

                _prev = new Image();

            }

 

 

            _prev.Source = BitmapFrame.Create(target).GetAsFrozen() as BitmapFrame;

 

            this.Content = _prev;

            _player.Stop();

            _player.Position = TimeSpan.FromSeconds(0);

        }

As you can see, we just renderred first video frame into Bitmap and put it as a source for our Image. Then we just put an image instead of text like "loading"

The next step is to draw video. The same player, giving us VideoDrawing, derrived from Drawing, that can be DrawingBrush for our rectangle element.

 

if (_video == null)

            {

                _video = new VideoDrawing();

                _video.Player = _player;

            }

            _video.Rect = frameRect;

 

            if (_frame == null)

            {

                _frame = new Rectangle();

 

 

                Brush brush = new DrawingBrush(_video);

 

 

                _frame.Fill = brush;

 

 

            }

But what's going on with the performance? You can not freeze this brush, it's dynamic, but you can cache it

RenderOptions.SetCachingHint(brush, CachingHint.Cache);

                RenderOptions.SetCacheInvalidationThresholdMinimum(brush, 0.5);

                RenderOptions.SetCacheInvalidationThresholdMaximum(brush, 2.0);

Fine, we almost done. A couple of problems, in order to get something from video, we should play it first, it is not really fun, but else, you have no even first frame. So, mute it before :) There are a couple of "nice adds" in this control, so it can be used as great start for your next video player.

That's all, kids. Have a nice day.

 

Source code for this article

How to change listbox, combobox or listview selection and background color

This is, probably, one of most common questions about styling in WPF - how to change selected item background color in ListBox (ComboBox, ListView etc). The answer is really simple - look into default control templates of those controls. Those templates using system brushed in order to paint background colors. So, if you want to customize an appearance of those controls, you always can rewrite templates, but why to do it if the only thing you want is to change color brush of elements, so KISS [keep it stupid simple] rewrite those system brushed like this:

      <ComboBox Background="Red">

        <ComboBox.Resources>

          <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Yellow" />

          <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Green" />

          <SolidColorBrush x:Key="{x:Static SystemColors.WindowBrushKey}" Color="Red" />

        </ComboBox.Resources>

        <TextBlock>TEST</TextBlock>

        <TextBlock>TEST</TextBlock>

        <TextBlock>TEST</TextBlock>

        <TextBlock>TEST</TextBlock>

        <TextBlock>TEST</TextBlock>

        <TextBlock>TEST</TextBlock>

        <TextBlock>TEST</TextBlock>

      </ComboBox>

That's all, folks. Now you have you own colored combo, that looks like this:

Thursday, April 05, 2007

Read your data easily from application resources

"I put my images and xaml vectors into projects resources and how I do not know how to get it" - this one of most common customers' questions. Really, how to get something exists in application resources, and the most important question is how to do it easy way? So, let's start.

Create new project. By using VS Solution Explorer create resource directory inside your project directory. Drag your images from Windows Explorer into this directory. Now create a couple of XAML vector elements or pages by using Microsoft Expression Suite and drop them into the same directory. Now let's take a close look to the properties of those files, all images become resources, when all xamls - pages. You do not really need them in this case as pages, so change their Build Action into Resource as well as appears within images.

So far so good. Now you have to pick them from your application. In order to get binary stream of our resources we'll have to use GetResourceStream method of Application. But stop. We have to do it as number of items we want to get. We have to hardcode it. Why not to build handy static method, that receives the resource path as parameter and return us the type we need. Good idea. The brilliant feature of .NET 2.0 and up is generics, and this is the "king case" to use them within this method. Let's do it.

 

static T loadResource<T>(string path)

        {

            T c = default(T);

            StreamResourceInfo sri = Application.GetResourceStream(new Uri(path, UriKind.Relative));

 

            if (sri.ContentType == "application/xaml+xml")

            {

                c = (T)XamlReader.Load(sri.Stream);

            }

            else if (sri.ContentType.IndexOf("image") >= 0)

            {

                BitmapImage bi = new BitmapImage();

                bi.BeginInit();

                bi.StreamSource = sri.Stream;

                bi.EndInit();

                if (typeof(T) == typeof(ImageSource))

                {

                    c = (T)((object)bi);

                }

                else if (typeof(T) == typeof(Image))

                {

 

                    Image img = new Image();

                    img.Source = bi;

                    c = (T)((object)img);

                }

            }

 

            sri.Stream.Close();

            sri.Stream.Dispose();

 

            return c;

        }

We done. Now, all you have to do is to call something like this to bring image source.

 

Image img = new Image();

img.Source = loadResource<ImageSource>("Resources/myImage.png");

Or something like this to bring FrameworkElement.

 

Window w = new Window();

w.Content = loadResource<Border>("Resources/myBorder.xaml");

((Border)w.Content).Child = loadResource<StackPanel>("Resources/myStackPanel.xaml");

((StackPanel)((Border)w.Content).Child).Children.Add(img);

Have a good day, dudes. Take this day to extend our magic resources loader class to another content types (if needed)

Binding to binding of two listboxes

Let's imagine following thing. I have list of projects and list of employees, assigned to those projects. Now I want to select some projects and see employees' assignments - who's going to work overtime. Simple, yes? We just create two listboxes. One binded to the list pro projects, and the second is binded to the selected items of first listbox with converter, that calculate unique employees and return calculated result. Something like this. Here we can clearly see, that Employee 12 already forgot how his kids looks like.

 

      <ListBox Name="prjs" SelectionMode="Extended" ItemsSource="{StaticResource projects}"/>

      <ListBox Name="emps1" ItemsSource="{Binding ElementName=prjs, Path=SelectedItems, Converter={StaticResource myConv}}"/>

Do it. You'll notice very fast, that your converter myConv will be called only once, when you have nothing selected, so it simple impossible to convert selected values to something, we need.  You know what, it makes sense, why I should reinitialize converter, if my types are not going to change?

Let's try another trick. We'll put shared datasource in application resource level and on SelectionChanged event put our stuff there. After it we'll just bind second listbox to this source.

 

<l:Employees x:Key="employees"/>

<l:Projects x:Key="projects"/>

 

 

<ListBox Name="prjs" SelectionMode="Extended" ItemsSource="{StaticResource projects}" SelectionChanged="onSelection"/>

<ListBox Name="emps" ItemsSource="{Binding Source={StaticResource employees}}"/>

 

 

void onSelection(object sender, SelectionChangedEventArgs e)

        {

            Employees emps = this.Resources["employees"] as Employees;

 

            if (emps != null)

            {

                for (int i = 0; i < e.RemovedItems.Count; i++)

                {

                    Project p = e.RemovedItems[i] as Project;

                    for (int j = 0; j < p.Employees.Count; j++)

                    {

                        if (emps.Contains(p.Employees[j]))

                        {

                            if(emps[emps.IndexOf(p.Employees[j])].Width == 0)

                            {

                                emps.Remove(p.Employees[j]);

                            }

                            else

                            {

                                emps[emps.IndexOf(p.Employees[j])].Width--;

                            }

                        }

                    }

                }

 

                for (int i = 0; i < e.AddedItems.Count; i++)

                {

                    Project p = e.AddedItems[i] as Project;

                    for (int j = 0; j < p.Employees.Count; j++)

                    {

                        if (!emps.Contains(p.Employees[j]))

                        {

                            emps.Add(p.Employees[j]);

                        }

                        else

                        {

                            emps[emps.IndexOf(p.Employees[j])].Width++;

                        }

                    }

                }

            }

        }

Now it's works. The only thing we have to do is create simple datatemplate to visualize an employee assignment.

 

    <DataTemplate DataType="{x:Type l:Employees}" x:Key="emp">

      <TextBlock FontSize="{Binding Path=Width, Converter={StaticResource wts}}" Text="{Binding Path=Name}"/>

    </DataTemplate>

That's all folks, not always we should go straight-forward to get what we need from WPF, but it's looks like no really obstacles can be found there.

Source code for this article

Wednesday, April 04, 2007

Expression Blend and Web are available on MSDN

For all those who asked about how goes licensing of Microsoft Expression Suite, the answer is, that both Blend and Web Expression are available for MSDN Premium users. Great news.

Sunday, April 01, 2007

Too much "async" with this WPF

What's this post about? This post begins with DIY. Try to do the following:

  1. Add simple ListView to your application
  2. Inside the ListView add two TextBlocks
  3. For each TextBlock subscribe to OnMouseDown (or any other mouse event)
  4. OnMouseEvent create new Window and open it with Show() method.

Did it? Fine. Have you paid your attention, that each new window opens this way goes backward? Don't it looks a bit strange?Let's give ListView other try.

  1. Add simple ListView to your application
  2. Inside the ListView add two TextBlocks
  3. Subscribe to SelectionChanged event of the listview
  4. OnSelectionChanged create new Window and open it with Show() method.

The same result? Maybe all collections have this bug behavior? Let's give a try to LiveBox... Do the same as in previous try, but instead of ListView use ListBox. The behavior disappears. Now the new window opens normally over the main window. What's the difference?

Return to second try (with SelectionChanged event), but put SelectionMode property of ListView to "simple" or "multiple". Works fine, right. So the problem with SelectorItem selection. Let's dive a bit deeper.

What's actually happens? If you have rather slow computer, you can clearly see the process. First occurs "mouse event", then opens window at the front of openner, next the item becomes selected and steals focus. Why ListViewItem doing it? According my short investigation, the root of the problem is Automation boundary. If you have time and wish to do it, trace all events (by using global events handling) and see it yourself. If you have either time nor wish, the answer is following.

WPF process almost any operation asynchronously, so with new window opening, the item becomes selected  and grab focus. How to solve this. Simple. Do your work very asynchronous way. Following the code, that opens window with normal thread priority, so it waits for openner to finish all tasks to process new job.

 

delegate void OpenWindowDelegate(bool bringFocus);

        void OpenWindow(bool bringFocus)

        {

            if (!bringFocus)

            {

                Window1 w = new Window1();

 

                w.Show();

            }

            else

            {

                this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,

                    new OpenWindowDelegate(OpenWindow), false);

            }

 

 

        }

We finished, now your windows will be opened above the opener and you're be able to do anything with both of those windows (ShowDialog() method or setting owner property for opened window will works fines, even without it, 'cos it locks the focus on newly opened window.

Happy Passover to all of you. Oh, I almost forgot my original question: Does this behavior is bug or not? I'm really interested with your opinion.

Source code for this article