DCSIMG
April 2007 - Posts - Just code - Tamir Khason

April 2007 - Posts

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/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

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/how-to-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:

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/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)

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/binding-to-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

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/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.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/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 

More Posts « Previous page