Blog Home  Home Feed your aggregator (RSS 2.0)  
Eric Malamisura's Blog - WPF
Geek Ramblings
 
 Saturday, July 14, 2007

Was messing with the ListView control in WPF as usual and could not for the life of me figure out how to get the Image source attribute (Image is a separate control type) to show the image of a bitmap that I pulled from an icon from the system.  Basically what I was doing was I was using SHGetFileInfo to retrieve the icon of a particular file, I have two columns in my ListView.  The first column holds a <Image> element the second column holds a <TextBlock> element which displays the path.  I wanted the first column to display the system icon of the second column, so I retrieved this value through the windows API SHGetFileInfo (unmanaged) and got back an Icon which I then convert to a Bitmap.  I wrote a helper utility that does all of this, it also caches the images for common file extensions to prevent excessive system fetching. 

But setting the source in XAML to the property of type Bitmap does nothing, it turns out you have to set the source to the path of the image.  Well my question was, how do you specify the path of an image that you have in memory?  Also then how do you specify the path to XAML?  Well it turns out you can write something called a Converter that will convert the value of anything you want before the binding actually takes place.  Using the following XAML syntax to setup the binding, there are two steps, first you setup the local resource, second you specify the converter to use.

Resource:

   1:  <Window.Resources>
   2:      <custom:ImageConverter x:Key="imageConverter" />
   3:  </Window.Resources>
 

Binding Syntax:

<Image Source="{Binding Path=Image, Converter={StaticResource imageConverter}}" ... >

Next you must write the converter, since the convert I am using is read only the ConvertBack method does nothing.  There does not appear to be a read-only version of this interface so this is the best I could think of, in any case it works.  There is another interface called IMultiValueConverter, from what I gather it converts an array of objects handed to it.

The convert I wrote that takes a Bitmap and returns the source path:

   1:  [ValueConversion(typeof(Bitmap), typeof(string))]
   2:      public class ImageConverter : IValueConverter
   3:      {
   4:          public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   5:          {
   6:              return Imaging.CreateBitmapSourceFromHBitmap(((Bitmap)value).GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
   7:                  BitmapSizeOptions.FromEmptyOptions());
   8:          }
   9:   
  10:          public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  11:          {
  12:              return null;
  13:          }
 

It returns a BitmapSource which it turns out is one of the types that you can bind to with the Image Source attribute in XAML, so this works.  So first off you do not want to use this for Resource files, you should use the built in WPF support such as setting the path to its package location:

"pack://application:,,/Resources/file.ico"

Which will work as long as you don't set the location of the resource to "Embedded Resource" since that is not supported.  If you need to store vector based graphics you can use the Resource Management support of WPF but if you can't for valid reasons than the above option seems to be your only one.

 

Saturday, July 14, 2007 7:48:59 PM (GMT Standard Time, UTC+00:00)  #    Comments [1]    |   | 
 Saturday, July 07, 2007

In a previous post I mentioned I couldn't figure out how to get a column to stretch the width of the control when it was expanded.  Well I stumbled onto a binding feature that is very very cool, its called RelativeSource and it lets you bind to properties on the control itself using the Self attribute, or the one I care about is child controls.

So in all its glory, the code that I use to set to an arbitrary number, is now set to a number that auto increments when the controls width is changed which you can verify by placing it in a text block.

   1:  Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, AncestorLevel=1}, Path=ActualWidth}"

So yes it is verbose, and yes at first glance its confusing but it makes sense when you understand what its doing.  It's just going one level down to its container, and then I am getting the ActualWidth of that container.  Why not width?  Well I don't set the width on that control so the width will be null actually, in WPF width does not give you the Width of the control.  Instead it gives you the width that you have set it to if ever, and ActualWidth gives you the current active width of the control.

Today has been a HUGE WPF day for me, I have spent the entire day working with it and its exciting.  I hope to share all my findings with everyone that needs it.  It is very difficult to find information on how to do a lot of the stuff I am posting about, most of it has taken me hours upon hours of trial and error to get it working.

Saturday, July 07, 2007 3:35:14 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]    |   | 

In WPF the beloved and well known InvokeRequired method has disappeared from the framework.  Replacing it is the Dispatcher, and it's methods CheckAccess and VerifyAccess pretty much are the replacement for InvokeRequired.

So if you have done any new WPF work you will quickly think, what the hell am I talking about.  Because if you type Dispatcher and wait for intellisense you don't see these methods, well that's because they are explicitly hidden from intellisense.  One can assume they did this intentionally to prevent developers from overusing these methods, since every WPF object will periodically call these methods on their own it's not necessary for you to check manually.  Unless you are developing your own WPF object, at least that's my best guess.

They have actually marked the Dispatcher.CheckAccess and Dispatcher.VerifyAccess with the [EditorBrowsable(EditorBrowsableState.Never)] (thanks Karsten Januszewski)attribute so you can see that they are intentionally hiding this from view.  I hope more light is shed on this topic as it's quit confusing.  Also there is a hack circulating for a way to check if the executing thread needs to be marshalled or not by doing a comparison against the UI thread.  To me that code really makes me feel dirty just by looking at it.

So how do you use the Dispatcher?  Well there are numerous different ways, I prefer to use an anonymous delegate:

   1:   Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate
   2:  {
   3:    //Update UI 
   4:  });

You don't have to use the ThreadStart delegate, the reason I use it is simply because it takes no parameters.  You can also obviously point it at a method, or itself which if you do that you will need to use CheckAccess in that instance.  There is a very valid performance reasons controls don't check to see if they are on the correct thread every single time.  If you do it the second way you incur that overhead, so out of curiosity I ran a benchmark comparing the two styles.

In the first benchmark I used the method above, and got the following results (in milliseconds):

1st Run

2nd Run

3rd Run

4th Run

5th Run

6th Run

Average

10368ms

9483ms

9750ms

9531ms

9559ms

9511ms

9700ms

10644ms

9873ms 9845ms 10009ms 9830ms 9876ms 10012ms
 
I find it interesting, the additional check using CheckAccess incurred about 300ms of additional processing time in a loop execution of 100,000 iterations. Using the below algorithm for keeping the CPU busy, remember this was a UI test so I needed an event to fire on he UI side from a separate thread and get marshalled to the UI thread.  None of that code changed between the benchmarks, the ONLY code I changed was the UI thread, and how it handled the marshalling.
 
 
 
The difference seems negligible and not worth really any effort to avoid, however if you are working with millions, or billions of records that require the UI to updated for each item (why?) then you might want to take this optimization into account.  Unlikely as the situation really is it's interesting... 


Algorithm used for benchmark (simulates a real world data event, think of the DataGrid):

   1:  ArrayList al = new ArrayList();
   2:  for (int i = 0; i < 100000; i++)
   3:  {
   4:    al.Add("Test");
   5:    if (NewItem != null)
   6:      NewItem(this, EventArgs.Empty);
   7:  }

Is this a 100% solid benchmark? Probably not, in fact someone else might be able to get differing results but it does show a difference which is really quit obvious.  You are calling an additional method so you would expect it to take slightly longer but it also illustrates why they don't call this on the WPF object for every single method call natively.

Saturday, July 07, 2007 1:07:53 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]    |   | 
 Friday, July 06, 2007

So far my experience with WPF has been well met, it is an extremely flexible system with a nearly limitless possibilities with customizing controls.  However, with flexibility often comes complexity.  So I have been messing with the ListView control and was trying to figure out how to hide the column headers, is it possible?  It didn't seem like the control even supported it...But then I remembered reading about Styles and how you can set nearly any property with a style, on any control whether its nested deep in the controls hierarchy or not.

So that's exactly what I did, and you can see my XAML for that below:

   1:  <GridView>
   2:    <GridView.ColumnHeaderContainerStyle>
   3:     <Style TargetType="GridViewColumnHeader">
   4:       <Setter Property="Visibility" Value="Hidden" />
   5:       <Setter Property="Height" Value="0" />
   6:     </Style>
   7:  </GridView.ColumnHeaderContainerStyle>


If there is an easier way to do that then I am all ears, please share the knowledge!

The next step in my customizing quest was I wanted two columns, the first one to show an icon, the second one to show text.  However, the ListView control was inside of a grid column with a grid splitter so this column could get bigger and the control along with it.  Which was desired, if you make the control bigger I wanted the text that was Ellipsed due to not enough space to show. 

This presented a problem, first how do you get the second column to take up the remaining room of the control?  Secondly, how do you get the text to ellipse when the control is cutting the text off?

The first one was not so easy to solve, and I actually never came up with a solid solution, more of a hack really and it only works since in my instance I don't need to have horizontal scrolling ability inside my ListView control. So the solution was to hide the horizontal scrollbar and set the column width to a very high number, which works but it's really a dirty solution.  Until someone can offer me a better solution this is the only one I could come up with.

Note: I solved this problem without the hack in a later post, you can view it here.

Ellipse Text XAML:

   1:  <GridViewColumn.CellTemplate>
   2:   <DataTemplate>
   3:    <Label HorizontalAlignment="Stretch">
   4:    <TextBlock Text="{Binding Path=Name}" Width="Auto" HorizontalAlignment="Stretch" TextTrimming="CharacterEllipsis" />
   5:     <Label.ToolTip>
   6:      <ToolTip>
   7:       <TextBlock Text="{Binding Path=Name}" />
   8:      </ToolTip>
   9:     </Label.ToolTip>
  10:    </Label>
  11:   </DataTemplate>
  12:  </GridViewColumn.CellTemplate>


Hide Horizontal Scrollbar XAML:

<ListView ItemsSource="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">


Last but least, normally each ListViewItem is only large enough to fit the contents.  In order to avoid this we set the ListView.ItemContainerStyle as seen below:

   1:  <ListView.ItemContainerStyle>
   2:   <Style TargetType="{x:Type ListViewItem}"><Setter Property="HorizontalContentAlignment" Value="Stretch" />
   3:   </Style>
   4:  </ListView.ItemContainerStyle>


So that's basically it for now, if you have any cool stuff you have find be sure to send them my way.  I am gathering that WPF is going to be a huge learning curve for everyone, going to take quit a while to get elite at this stuff.  Designers will be able to have a field day, but more often than not us poor developers are left doing both design and development.

Friday, July 06, 2007 9:34:09 PM (GMT Standard Time, UTC+00:00)  #    Comments [2]    | 
Copyright © 2008 Eric Malamisura. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.