August 2009 - Posts
Last few days I’ve upgraded the XP2Win7 Trigger Start Service demo application UI to WPF. Unfortunately I had no time to add the copy images from USB animation. So just in case that you write kind of copy file application here is a WPF animation I made:



Note that this animation demonstrates how to render a 2D element as 3D by using the WPF Viewport2DVisual3D with animation.
You can download the code from here.
In this post I would like to share with you a simple solution for searching and highlighting text in the WPFToolkit or WPF 4.0 DataGrid.
The Problem
Lets say for example that you have a DataGrid with several columns that may display text. Now you want to be able to search terms in these text parts. For example:
Proposed Solution
1. Create an attached property of type string called SearchTerm for attaching the data-grid with the search term provided by a TextBox (see images).
<TextBox x:Name="SearchBox" ... />
<tk:DataGrid local:SearchOperations.SearchTerm=
"{Binding ElementName=SearchBox, Path=Text}" ...>
The SearchTerm attached property is defined as Inherits, so it could be easily extracted from any cell within the grid.
2. Calculate each cell data against the search term, and give a visual feedback accordantly.
- An easy way is to attach each cell with a boolean property that will be set to true in case that there is a match, or false otherwise. Then create a cell style trigger that yields visual feedback based on this attached property value.
<Style x:Key="FirstNameCell" TargetType="{x:Type tk:DataGridCell}">
<Setter Property="local:SearchOperations.IsMatch">
<Setter.Value>
<MultiBinding Converter="{StaticResource SearchTermConverter}">
<Binding Path="FirstName" />
<Binding RelativeSource="{RelativeSource Self}"Path="(local:SearchOperations.SearchTerm)" />
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="local:SearchOperations.IsMatch" Value="True">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="#FF84FF7A" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
To set the IsMatch attached property with the correct value, I’ve bound it with a multi-binding expression composed of two binding expressions using a multi-value converter:
- The value represented by the cell
- The search term attached property
- The Multi-value converter check if match exist
public class SearchTermConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
var stringValue = values[0] == null ? string.Empty : values[0].ToString();
var searchTerm = values[1] as string;
return !string.IsNullOrEmpty(searchTerm) &&
!string.IsNullOrEmpty(stringValue) &&
stringValue.ToLower().Contains(searchTerm.ToLower());
}
...
#endregion
}
Now that we’ve extended the WPF data-grid control with extra SearchTerm and IsMatch properties we can search terms in the grid and provide visual feedbacks using styles.
You can download the code from here, and the WPFToolkit from here.
The IDCC (Israeli Developers Community Conference) is coming soon and you, part of this great community have an opportunity to decide what sessions are relevant.
My suggested topic is of course WPF 4.0
When Visual Studio 2010 Met WPF...
We've been waiting for this moment for so long: Microsoft take a strategy decision to use WPF platform as the main UI technology for Visual Studio 2010. This is a great news and a sign that WPF is now ready for business applications. In this session we will take a tour of what's new in WPF 4.0 Beta1 and see how to change parts of the Visual Studio 2010 with WPF.
If you like my topic, you are welcome to vote for it here. It will be my pleasure.
Also you may find my colleagues topics very interesting:
Patterns of a successful application
Adding Maps and Geo features to Silverlight Application is Easy
Hope to meet you there…
I’ve been playing around with MEF lately and I have to say that it has a great potential for building Composite UIs.
One of the sample applications arrived with MEF Preview 6 (called MEFLook), demonstrates how to implement kind of Outlook composite application based on WPF. The interesting part of this application is that views can be imported into the main window as dynamic parts, simply by decorating them with the MEF Export attribute.
<Window x:Class="MeflookSample.MeflookShell" ...>
<DockPanel Name="dockPanel">
...
</DockPanel>
</Window>
[Export("MainWindow", typeof(Window))]
public partial class MeflookShell : System.Windows.Window
{
[ImportMany("MeflookView")]
private Lazy<UserControl, IShellViewMetadata>[] views = null;
private void OnLoaded(object sender, RoutedEventArgs e)
{
foreach (var view in views.OrderBy(i => i.Metadata.Index))
{
dockPanel.Children.Add(view.Value);
}
}
}
In the proposed solution, each window has to load its own dependencies (view/parts) by using the ImportManyAttribute attribute and then add them manually to the correct panel or placeholder.
This method has several disadvantages:
- Placeholders (panels) created in XAML have to be exposed to and interact by the code behind
- Each window (shell) has to have specific logic for loading the imported views into placeholders
- Presenters (MVVM) are not supported since panels do not support non UIElement types
To overcome these disadvantages I’ve developed a technique called “Declarative Import”. This technique provides an option to import views/presenters via XAML based on attached properties.
<Window x:Class="ImportFromXaml.Parts.Shell.PlayerShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mef="clr-namespace:ImportFromXaml"
xmlns:c="clr-namespace:ImportFromXaml.Parts.Player">
<DockPanel>
<ContentControl mef:CompositionServices.Import="{x:Type c:IRibbonControl}"
DockPanel.Dock="Top" />
<Expander Header="Parts" IsExpanded="True" DockPanel.Dock="Bottom" Height="200">
<TabControl mef:CompositionServices.Import="{x:Type c:IPlayerPart}"
Height="200">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding DisplayHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
</Expander>
<ContentControl mef:CompositionServices.Import="{x:Type c:IPlayerControl}"
DockPanel.Dock="Bottom"
Height="100" />
</DockPanel>
</Window>
As you can see, the CompositionServices.Import is an attached property that has at least one parameter: Contract Type. Attaching this property to supported elements automatically imports one or many exports, and add them to the target. Views can be either UIElement or any CLR type such as Presenter.
Lets take look at the CompositionServices type:
public static class CompositionServices
{
private static CompositionContainer Container { ... }
public static Type GetImport(DependencyObject obj)
{
return (Type)obj.GetValue(ImportProperty);
}
public static void SetImport(DependencyObject obj, Type value)
{
obj.SetValue(ImportProperty, value);
}
public static readonly DependencyProperty ImportProperty =
DependencyProperty.RegisterAttached(
"Import",
typeof(Type),
typeof(CompositionServices), new UIPropertyMetadata(null, ImportPropertyChanged));
private static void ImportPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var adapters = Container.GetExportedValues<IViewAggregatorAdapter>();
var adapter = adapters.FirstOrDefault(a => a.CanAdapt(d));
if (adapter == null)
{
return;
}
adapter.Adapt(d);
var views = Container.GetExportedValues(e.NewValue as Type);
adapter.AddViews(views);
adapter.Release();
}
#endregion
}
Whenever Import attached property is attached to an element, the ImportPropertyChanged method is activated and does the following:
1. Export view aggregator adapters by using the IViewAggregatorAdapter. A view aggregator adapter is a simple adapter that knows how to add view/s into the target element. I’ve created two adapters, one for the ContentControl and the other for ItemsControl.
2. Extract the first adapter that knows how to deal with the target.
3. Call an extension method that get all exports match to the given contract type.
4. Use the adapter to add all export values into the target.
Now that we can import parts from XAML we can create kind of Composite UI declaratively. Of course there is a lot of work to do making this a production tool.
So I hope that Prism V-Next will use MEF in its guts. This solution is only a simple thought.
You can download the solution from here.
One of my blog readers sent me an email regards how to create Binding to an object via XAML, where Path is unknown at design time.
For example, you want to bind to Source.Property property, where Property is provided by another property.
public partial class Window1 : Window
{
public Window1()
{
Path = "Name";
Person = new Person()
{
Name = "Tomer Shamam"
};
DataContext = this;
InitializeComponent();
}
public string Path { get; private set; }
public Person Person { get; private set; }
...
}
<TextBox Text="{Binding Path=???}" />
As you may guess correctly, we can’t bind to dynamic resolved path, unless you create the binding from code and explicitly specify the path (which is not always possible, and not recommended by design).
To solve this problem, I’ve just created a custom Binding object called BindingEx. BindingEx has two new properties: SourcePath and PathOrigin.
SourcePath – The path to the source object.
PathOrigin – The path to the dynamic resolved path.
Using BindingEx the markup above should be look like this:
<TextBox Text="{t:BindingEx SourcePath=Person, PathOrigin=Path}" />
As you can see, the Window1 instance is in the DataContext so we don’t have to specify source (but we can if we want to). SourcePath indicates that the binding should look for the source object in the Person property (which belongs to Window1 in this case), and PathOrigin points to the Path property (which also belongs to Window1 instance). This is where binding should extract the path to be bound to.
You may download my custom binding from here. Note that I didn’t check all the possible cases (there are many), so use it at your own risk.