When Selector.IsSynchronizedWithCurrentItem will be true?

| 4 comments

This looks like an obvious question, you when set it to true either through XAML or code, then the Selector's (such as ListBox and ComboBox) selection operation will drive the setting of the current item within CollectionView which presents the data bound source collection, so when you want to synchronize current item with the selected item, you could write something like the following XAML snippet:

<
Pagexmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:sys="clr-namespace:System;assembly=mscorlib">
  <
Page.Resources>
    <
x:Arrayx:Key="data" Type="{x:Type sys:String}">
      <
sys:String>Paviel Dedved</sys:String>
      <
sys:String>Andriy Shevchenko</sys:String>
      <
sys:String>Paolo Maldini</sys:String>
      <
sys:String>Robert Baggio</sys:String>
      <
sys:String>Alessandro Del Piero</sys:String>
    </
x:Array>
  </
Page.Resources>
  <
StackPanel DataContext="{StaticResource data}">
    <
ListBox ItemsSource="{Binding}" Margin="5" IsSynchronizedWithCurrentItem="True"/>
    <
ComboBox ItemsSource="{Binding}" Margin="5" IsSynchronizedWithCurrentItem="True"/>
    <
TextBlock>CurrentItem: <TextBlock Text="{Binding /}"/>
    </
TextBlock>
  </
StackPanel>
</
Page>

From the above code, the ListBox and ComboBox is data bound to the same source collection, but for each ItemsSource binding, a unique CollectionView is generated, so that both ListBox and ComboBox can make independent selection without affecting each other, this is what most developers expect. So if you want both ListBox and ComboBox to drive the currency, you need to explicitly set the IsSynchronizedWithCurrentItem property to true, this is pretty obvious and straightforward, isn't it?

If you think this is an obvious thing to do, then I have another XAMLPad ready example which might confuse you:

<
Pagexmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:sys="clr-namespace:System;assembly=mscorlib">
  <
Page.Resources>
    <
x:Arrayx:Key="data" Type="{x:Type sys:String}">
      <
sys:String>Paviel Dedved</sys:String>
      <
sys:String>Andriy Shevchenko</sys:String>
      <
sys:String>Paolo Maldini</sys:String>
      <
sys:String>Robert Baggio</sys:String>
      <
sys:String>Alessandro Del Piero</sys:String>
    </
x:Array>
    <
CollectionViewSource x:Key="src" Source="{StaticResource data}"/>
  </
Page.Resources>
  <
StackPanel DataContext="{StaticResource src}">
    <
ListBox ItemsSource="{Binding}" Margin="5"/>
    <
ComboBox ItemsSource="{Binding}" Margin="5"/>
    <
TextBlock>CurrentItem: <TextBlock Text="{Binding /}"/>
    </
TextBlock>
  </
StackPanel>
</
Page>

If you run above XAML snippet in XAMLPad, you will find that both ListBox and ComBox can drive the currency as if the IsSynchronizedWithCurrentItem property is set to true. so what happens?

If you use reflector to example the source code of the Selector, if you will the SetSynchronizationWithCurrentItem() method implementation as follows:

private void SetSynchronizationWithCurrentItem()
{
    bool? isSynchronizedWithCurrentItem = IsSynchronizedWithCurrentItem;
    bool oldSync = IsSynchronizedWithCurrentItemPrivate;
    bool newSync;

    if (isSynchronizedWithCurrentItem.HasValue)
    {
        // if there's a value, use it
       
newSync = isSynchronizedWithCurrentItem.Value;
    }
    else
   
{
        // when the value is null, synchronize if selection mode is Single
        // and there's a non-default view.
       
SelectionMode mode = (SelectionMode)GetValue(ListBox.SelectionModeProperty);
        newSync = (mode == SelectionMode.Single) &&
        !CollectionViewSource.IsDefaultView(Items.CollectionView);
    }

    IsSynchronizedWithCurrentItemPrivate = newSync;

    if (!oldSync && newSync)
    {
        SetSelectedToCurrent();
    }
}

From the bolded line of code, it seems that this is an intentional behaviour, which means that if the SelectionMode of Selector is Single, and the CollectionView data bound to the Selector is not the implicit, default View generated by WPF's data binding engine, Selector will drive the currency. This might not make sense to you, but after the following explanation, I hope it finally makes sense to you.

There are two aspects for this issue, the first is multi selection, and the second is non-default view.

First, it's no brainer that when the Selector is in Multiple SelectionMode, WPF can not take on current synchronization, because there is no such concept of "multi-current items" from the perspective of each ICollectionView implementation(CollectionView for IEnumerable, ListCollectionView for IList and BindingListCollectionView for IBindingList etc), you might think that WPF can choose one item from the selected items as the current item, but it's hard to figure out which one is the best candidate for current item, because this decision usually varies tremendously in different scenarios, and it's nearly impossible for a general purpose framework like WPF to make a good decision for you.

Second, WPF drives currency if you are using the non-default explicit CollectionView, this usually is achieved by declaring the underlying data to be CollectionViewSource, because when you use CollectionViewSource which is a XAML friendly version of CollectionView, WPF assumes that you are mostly likely wanting to enable master/detail binding, and it's obvious that you need the currency synchronization service provided by Selector. So WPF thinks that it's a good default and correct behaviour to implicitly turn on currency synchronization for you.

Knowing that this is the heuristics WPF makes on how you are going to use the data, and it might cause some incorrect behaviour as reported in this WPF MSDN forum thread, so it might worth a blog entry to point this out:)

BTW, for any one who thinks that the {Binding /} syntax is alien to them, you could refer to this Ian Griffith's excellent blog post for explanation.

The Power of Shader - Part One

| 2 comments

.NET Framework 3.5 SP1 Beta1 has been released, this release is not simply a service pack release as we've known for many years of what  SP actually means. It seems that Microsoft has changed its strategy, and start to include new features in SP release.

One of the most exciting feature of this release is a new hardware accelerated, pixel shader based custom effect architecture which drives the software, WIC based bitmap effect architecture into obsolete. This new architecture has tackle many of the performance problems inherent with previous stale architecture. On one hand, the new custom effect will run in composition thread rather than UI thread, which means that it will not tie up the UI thread as the previous effect does, on the other hand, it will be hardware accelerated if your GPU supports pixel shader 2.0 or above. If you don't have decent GPU, the effect will be emulated using software, RenderTargetBitmap is a special case, if any visual which has effect applied to it needs to be rasterized into the RenderTargetBitmap, it will go through software rendering path.

If you have read the blog series written by Greg Schechter about the new shader effect feature, you could have been familiar with how to create simple effects. But those shader effect expects you to pass in the pre-compiled HLSL bytecode, which really requires you to use the shader compiler (fxc.exe) which is shipped with the DirectX SDK to compile the shader first, then pass it to the ShaderEffect derivative's constructor. How about if I want to directly pass in the source shader string, and have it get compiled and executed at runtime, this is called online compilation as compared with offline compilation with fxc.exe tool. Fortunately, I could use the Microsoft.DirectX.Direct3D.ShaderLoader class which is part of the managed DirectX API which can do exactly what I want, Here is the ShaderEffect implementation which supports online compilation:

public class SourceShaderEffect : ShaderEffect
{
    // The custom effect expects pixel shader 2.0 support.
    public static readonly String PixelShaderProfile = "ps_2_0";

    public static readonly ShaderFlags ShaderFlags = ShaderFlags.None;

    // We use "main" as the default function name for the shader.
    public static readonly String DefaultFunctionName = "main";

    /// <summary>
    /// Constructs a new SourceShaderEffect instance.
    /// </summary>
    /// <param name="shaderFilePath">path to the source shader file</param>
    /// <param name="functionName">shader entry function name</param>
    public SourceShaderEffect(String shaderFilePath, String functionName)
    {
        GraphicsStream stream = ShaderHelper.TryThrowCriticalException(() =>
        {
            return ShaderLoader.CompileShaderFromFile(
                shaderFilePath,
                functionName,
                null,
                PixelShaderProfile,
                ShaderFlags);
        });

        if (null != stream)
        {
            base.PixelShader = new PixelShader();
            base.PixelShader.SetStreamSource(stream);
        }
    }

    /// <summary>
    /// Constructs a new SourceShaderEffect instance.
    /// </summary>
    /// <param name="shaderStream">stream which contains the shader source code.</param>
    /// <param name="functionName">shader entry function name</param>
    public SourceShaderEffect(Stream shaderStream, String functionName)
    {
        GraphicsStream stream = ShaderHelper.TryThrowCriticalException(() =>
       {
           return ShaderLoader.CompileShaderFromStream(
               shaderStream,
               functionName,
               null,
               PixelShaderProfile,
               ShaderFlags);
       });

        if (stream != null)
        {
            base.PixelShader = new PixelShader();
            base.PixelShader.SetStreamSource(stream);
        }
    }

    /// <summary>
    /// Constructs a new SourceShaderEffect instance with default shader entry function name.
    /// </summary>
    /// <param name="shaderFilePath">path to the source shader file</param>
    public SourceShaderEffect(String shaderFilePath) : this(shaderFilePath, DefaultFunctionName) { }

    /// <summary>
    /// Constructs a new SourceShaderEffect instance with default shader entry function name.
    /// </summary>
    /// <param name="shaderStream">stream which contains the shader source code.</param>
    public SourceShaderEffect(Stream shaderStream) : this(shaderStream, DefaultFunctionName) { }
}

 

There is no fancy stuff in this code, with the above code, you could simply pass a file path or stream to the shader source code, and you are done with it. Let's create a simple effect to see if it actually works, the following is a Sepia shader which I learnt from this nice article:

sampler2D s0 : register(s0);
float4 main(float2 tex : TEXCOORD0) : COLOR
{
    float4 color = tex2D(s0, tex);
    float value = color[0] * 0.299f + color[1] * 0.587f + color[2] * 0.114f;
    return tex2D(s0, value);
}

Let's save it into a file called Sepia.fx, and add it as a resource file into the WPF project you create from visual studio. In XAML, you could place an Image element, and at the code behind, you could write something like the following:

image.Effect = CreateSourceShaderEffect("Sepia");


public static ShaderEffect CreateSourceShaderEffect(String shaderLocation)
{
    ShaderEffect effect = null;
    shaderLocation = "pack://application:,,,/Shaders/" + shaderLocation + ".fx";
    Stream shaderStream = Application.GetResourceStream(new Uri(shaderLocation, UriKind.Absolute)).Stream;
    if (null != shaderStream)
    {
        effect = new SourceShaderEffect(shaderStream);
    }

    return effect;
}

 

Then, it seems that you've got everything ready to run, but actually you don't, if you are running under 64 bit Windows, you need to set the visual studio build system to target X86 architecture instead, otherwise you will get a BadImageFormatException. This means that to enable online compilation, your Application should be 32 bit, and it will be emulated when running under 64 bit Windows through WOW64 layer, which might not be what you expect. so if you are targeting 64 bit Windows, and you want your WPF application to be 64 bit, this technique is not the way to go, otherwise, it could be an option for you.

The following is a full sample code with another two simple shader effects such as Invert, and Grayscale. On the next installment, I will try to take adventure on shader effects with input parameters and with multi-sampler blending when I learnt how to do them:)

Attachment:ShaderEffectDemo.zip

Applicatons = Markup + Code

| 1 comments

I was asked by one of Chinese leading ComSci book publishers to write a book review on Charles Petzold's classical WPF book Applications = Markup + Code, because they've just finished with translating this awesome book into Chinese. Charles is always one of my most respectable .NET gurus, and here is my humble attempt at commentting on his book:

Windows Presentation Foundation hits the Windows programming community quite tremendously from the very beginning of its CTP release not only because it's a much better and superior technology than the stale GDI/USER based UI Framework, but also because it brings in a new programming paradigm that most Windows programmers might have never thought of, that is Applications = Markup + Code.

Windows Presentation Foundation is Microsoft's first attempt at unifying the programming model both for Windows desktop and the Web going forward (with the introduction of Silverlight, you can employ the same programming concept and techniques to author Rich Internet Applications). Windows Presentation Foundation also enables a new workflow between developers and designers, with Windows Presentation Foundation, designers has become an integral part of the whole software development process, and they will have a vital say over the final appearance of the whole UI, because they are trained for this, and specialized at doing this. This can free the developers from concerning about something they are not good at, such as how to adjust the colour contrast to make the Button looks more esthetically pleasing.

Windows Presentation Foundation also integrates text, imaging, graphics (both 2D and 3D), animations, audio/video, documents, printing, and resources management APIs into a single framework, those powerful APIs and toolsets really enable developers to build rich visualization applications. The ability to seamlessly incorporate 2D content over 3D scenes and vice versa is simply stunning which gives possibility to write Northface Demo type of applications.

Charles Petzold brings in his years of Windows programming experiences to provide us with a hitchhikers' guide around Windows Presentation Foundation first from the perspective of code, then XAML (Extension Application Markup Language), which might be the ultimate UI definition language for Windows moving forward. From Chapter 1 to Chapter 17, Charles first introduces us the basic WPF controls and how they differ from traditional win32/Windows Forms counterparts, he also teaches us probably the most important core concepts of WPF such as Dependency Properties and Routed Events which is a little bit different from what we've known about properties and events in C#. He also gives us a de tour on how to write a custom layout panel to layout elements/controls in a predefined manner which is pretty useful when you want to roll up your own data presentation controls. In the unique Chapter 18, Charles aggregates what we've learnt from the previous chapters to tell us how to write a full-blown WPF application such as Notepad. From Chapter 19 to Chapter 31, the amazing XAML tour starts. Again in those Chapters, Charles still focus on the basic building block of WPF such as navigation, geometrical transformation, data binding, control templates and data templates etc, but this time, from the XAML perspective, he tells us how to declaratively specify WPF constructs, and how they relate to their code representations, which is very important, this can help developers to better understand XAML, what it can and cannot do.

Charles' book just covers hoards of WPF contents and concept which I cannot name it all, but suffice it to say that I find the book Applications = Markup + Code quite complete, informative and interesting to read. I think this book should be put onto every serious WPF developer's bookshelves, what you think?

WPF, Terminal Services & Bitmap Remoting

| 1 comments

Everybody who has been playing around with WPF for a reasonable period of time might have known that WPF Application Processing is actually split up into two threads, they are UI thread which runs the dispatcher loop, and processes any GUI, keyboard/mouse/ink input, animation, layout and user initiated messages/events, and there is a hidden unmanaged background thread which is responsible for composing the graphical content (both in hardware or software if fallback is needed), and presenting it to frame buffer for display.

The whole architecture of WPF is built around this two threads model, and it has some of the benefits as articulated by Chris Anderson in the two-year long Channel video. One of the benefits Chris mentioned is that with the two threads model, in terminal services scenario, we can have the UI thread running in the TS server, and have the composition thread (or render thread in other nomenclature) running at the TS client console. And the communication between the two threads are handed over to the TS/RDP protocol. This can enable one interesting scenario called primitive remoting or vector remoting. And since UI thread is running at the TS server side, and it maintains the visual tree and the composition is at the TS client side, so in order to keep the client screen up to date, the UI thread will send the edits to the composition tree over the TS/RDP protocol in a highly compressed manner, this not only saves the network bandwidth, because only the edits need to be remoted, but also speeds up the client processing. This type of server/client communication also holds true in the standalone WPF application, the difference is that those two threads are running at the same Win32 process, and the inter-thread communication mechanism is used instead of the TS/RDP wire. To enable primitive remoting, both the TS server and TS client should run under Windows Vista and with desktop composition is enabled, this requirement tremendously narrows down the scenario in which the primitive remoting could be leveraged.

The upcoming .NET Framework 3.5 SP1 release would change all of this, in particular, bitmap remoting with sub region update support will always be used even if the TS server and the TS client are equipped to support primitive remoting, primitive remoting can be as good or bad as bitmap remoting, and the scenario in which primitive remoting is enabled is quite rare, because most existing Windows servers such as Windows 2000 or Windows 2003 server families don't support primitive remoting. So for any developer who wants your WPF applications to run reasonably well under TS scenario and running under TS is an important scenario for your applications, you need to take implication of bitmap remoting into consideration beforehand, there are a couple of ways from which you can improve the performance of your WPF application in TS scenario:

  • Considering using as little animations as possible in your application, and if animations are indispensable, you could try reducing the DesiredFrameRate (The default value is 6O FPS) of each animations you need to use, usually 25~30 FPS should be enough, and if you don't need high fidelity animation experience, you can use a even lower frame rate.
  • Considering using solid color brushes instead of gradient or tile brushes.
  • Try reducing the number of "hidden redraws" your application needs to perform, "hidden redraws" will be overlaid by its most foreground redraws, but they will still be remoted which waste network bandwidth. You can visualize the "hidden redraws" using the Perforator (part of WPF Performance Suite) with "Show dirty-regions update overlay" CheckBox checked.

How to Reference Named Elements Within CellTemplate?

| 0 comments

Getting reference to the named elements within data template is a commonly requested feature of WPF. In the current version of WPF, there is no intuitive and straightforward way to do it, you need to either traverse the visual tree to compare the Name property of each FrameworkElements within the tree, or alternatively you can first off grab the reference to the ContentPresenter within the ControlTemplate, and call DataTemplate.FindName() passing in the ContentPresenter reference as the "templatedParent" argument. Accessing named elements within CellTemplate complicates the things a bit since each Cell will have different instantiation of CellTemplate, the following helper class gives a possible solution to this problem (I also posted it in this WPF MSDN forum thread):

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace Sheva.Windows.Component
{
    public static class ListViewHelper
  
{
        /// <summary>
        ///
Finds an element that has the provided identifier name within the specified cell template identified by the cellColumn and cellRow
      
/// </summary>
        /// <param name="listView">
the referenced ListView control</param>
        /// <param name="cellColumn">
the index of column from which the CellTemplate will be retrieved</param>
        /// <param name="cellRow">
the index of the row from which the specified ListViewItem will be retrieved</param>
        /// <param name="name">
The name of the requested element.</param>
        /// <returns>
The requested element. This can be null reference if no matching element was found.</returns>
      
public static FrameworkElement FindNameFromCellTemplate(ListView listView, Int32 cellColumn, Int32 cellRow, Stringname)
        {
            if(listView == null)
            {
                throw newArgumentNullException("listView");
            }

            if(!listView.IsLoaded)
            {
                throw new InvalidOperationException("ListView is not yet loaded");
            }

            if (cellRow >= listView.Items.Count || cellRow < 0)
            {
                throw new ArgumentOutOfRangeException("row");
            }

            GridView gridView = listView.View as GridView;
            if (gridView == null)
            {
                return null;
            }

            if (cellColumn >= gridView.Columns.Count || cellColumn < 0)
            {
                throw new ArgumentOutOfRangeException("column");
            }

            ListViewItem item = listView.ItemContainerGenerator.ContainerFromItem(listView.Items[cellRow]) as ListViewItem;
            if (item != null)
            {
                if (!item.IsLoaded)
                {
                    item.ApplyTemplate();
                }
                GridViewRowPresenter rowPresenter = GetFrameworkElementByName<GridViewRowPresenter>(item);

                if(rowPresenter != null)
                {
                    ContentPresenter templatedParent = VisualTreeHelper.GetChild(rowPresenter, cellColumn) as ContentPresenter;
                    DataTemplate dataTemplate = gridView.Columns[cellColumn].CellTemplate;
                    if(dataTemplate != null && templatedParent != null)
                    {
                        return dataTemplate.FindName(name, templatedParent) as FrameworkElement;
                    }
                }
            }

            return null;
        }

        private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
      
{
            FrameworkElementchild = null;
            for(Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
            {
                child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
                if(child != null && child.GetType() == typeof(T))
                {
                    break;
                }
                else if(child != null)
                {
                    child = GetFrameworkElementByName<T>(child);
                    if(child != null && child.GetType() == typeof(T))
                    {
                        break;
                    }
                }
            }
            returnchild asT;
        }
    }
}

This actually requires a bit of coding, hopefully future version of WPF in particular the ListView control could has a much better content model to make it more flexible and extensible than it is right now.

How To Search Text In WPF FlowDocument?

| 11 comments

This blog article is a reply to the recent WPF MSDN forum thread on how to efficiently search text in FlowDocument. The thread starter needs to have the same performance as the search feature in Visual Studio text editor. I don't know how Visual Studio IDE implements the search feature, but in terms of search in FlowDocument, because FlowDocument enables much richer content model, It's presumably much harder to achieve the same search performance as Visual Studio text editor.

I have to say that the code I posted in that thread apparently has a serious performance flaw, it introduces a lot of unnecessary iterations. After digging into this issue at the weekend, I finally come up with a method which can achieve the perceived performance, and I think this should be enough at most circumstance. Based on this method, I mocked up a sample code which shows how to perform find and replace feature in FlowDocument, because find and replace is a common feature every text editing tool should provide, this might help others who need this similar feature. The following shows the core code which perform the search:

/// <summary>
///
Find the corresponding<see cref="TextRange"/> instance
/// representing the input string given a specified text pointer position.
/// </summary>
///
<param name="position">the current text position</param>
///
<param name="textToFind">input text</param>
///
<param name="findOptions">the search option</param>
///
<returns>An<see cref="TextRange"/> instance represeneting the matching string withing the text container.</returns>
public TextRange GetTextRangeFromPosition(ref TextPointer position, String input, FindOptions findOptions)
{
    Boolean matchCase = (findOptions & FindOptions.MatchCase) == FindOptions.MatchCase;
    Boolean matchWholeWord = (findOptions & FindOptions.MatchWholeWord) == FindOptions.MatchWholeWord;

    TextRange textRange = null;

    while (position != null)
    {
        if (position.CompareTo(inputDocument.ContentEnd) == 0)
        {
            break;
        }

        if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
        {
            String textRun = position.GetTextInRun(LogicalDirection.Forward);
            StringComparison stringComparison = matchCase ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
            Int32 indexInRun = textRun.IndexOf(input, stringComparison);

            if (indexInRun >= 0)
            {
                position = position.GetPositionAtOffset(indexInRun);
                TextPointer nextPointer = position.GetPositionAtOffset(input.Length);
                textRange = new TextRange(position, nextPointer);

                if (matchWholeWord)
                {
                    if (IsWholeWord(textRange)) // Test if the "textRange" represents a word.
                    {
                        // If a WholeWord match is found, directly terminate the loop.
                        break;
                    }
                    else
                    {
                        // If a WholeWord match is not found, go to next recursion to find it.
                        position = position.GetPositionAtOffset(input.Length);
                        return GetTextRangeFromPosition(ref position, input, findOptions);
                    }
                }
                else
                {
                    // If a none-WholeWord match is found, directly terminate the loop.
                    position = position.GetPositionAtOffset(input.Length);
                    break;
                }
            }
            else
            {
                // If a match is not found, go over to the next context position after the "textRun".
                position = position.GetPositionAtOffset(textRun.Length);
            }
        }
        else
        {
            //If the current position doesn't represent a text context position, go to the next context position.
            // This can effectively ignore the formatting or embedded element symbols.
            position = position.GetNextContextPosition(LogicalDirection.Forward);
        }
    }

    return textRange;
}

The code above is part of my FindAndReplaceManager helper class implementation, you can refer to the attachment for the complete source code. The code should be pretty straightforward as I've commentted it. The FindAndReplaceManager can support search options such as FindOptions.MatchCase and FindOptions.MatchWholeWord, aka two commonly used search options. For simplicity, I don't implement reverse search, since this should be really straightforward, instead of using LogicalDirection.Forward, you could use LogicalDirection.Backward.

As I've said, the FindAndReplaceManager should be able to achieve perceived performance at most situation, if you need hard best performance. You'd better choose a more sophisticated search algorithm instead of the bare-bones "start-to-end" search algorithm as is illustrated in the code above.

Another alternative you could choose is the internal undocumented search API provided by WPF. The System.Windows.Documents.TextFindEngine class has a static "Find" method, this method is widely used in build-in document readers and viewers such as FlowDocumentReader, FlowDocumentPageViewer, and FlowDocumentScrollViewer. Because TextFindEngine has a much better understanding of the underlying document content structure, it should provide the hard performance benefit you expect. The following helper method shows how to use this method:

using System;
using System.Windows;
using System.Reflection;
using System.Globalization;
using System.Windows.Documents;

namespace Sheva.Windows.Documents
{
    [Flags]
    public enum FindFlags
    {
        FindInReverse = 2,
        FindWholeWordsOnly = 4,
        MatchAlefHamza = 0x20,
        MatchCase = 1,
        MatchDiacritics = 8,
        MatchKashida = 0x10,
        None = 0
    }

    public static class DocumentHelper
    {
        private static MethodInfo findMethod = null;

        public static TextRange FindText(TextPointer findContainerStartPosition,TextPointer findContainerEndPosition, String input, FindFlags flags, CultureInfo cultureInfo)
        {
            TextRange textRange = null;
            if (findContainerStartPosition.CompareTo(findContainerEndPosition) < 0)
            {
                try
                {
                    if (findMethod == null)
                    {
                        findMethod = typeof(FrameworkElement).Assembly.GetType("System.Windows.Documents.TextFindEngine").
                               GetMethod("Find", BindingFlags.Static | BindingFlags.Public);
                    }
                    Object result = findMethod.Invoke(null, new Object[] { findContainerStartPosition,
                    findContainerEndPosition,
                    input, flags, CultureInfo.CurrentCulture });
                    textRange = result as TextRange;
                }
                catch (ApplicationException)
                {
                    textRange = null;
                }
            }

            return textRange;
        }
    }
}

Because TextFindEngine.Find() is a non-public API, we should use a bit of reflection code to call it. If you are working on pesonal project, feel free to use it as an alternative, but never ever use this method in production code.

WPF should provide a much better built-in public API to perform search operation in FlowDocument. I don't know what type of future plan WPF team has, but from my educated guess, WPF should have a much better support on this in the near future.

Attachment: SearchInFlowDocumentDemo.zip

DataErrorValidationRule - New Way To Invalidate Data In WPF

| 1 comments

The WPF 3.5 has introduced a new data validation API aka DataErrorValidationRule, which you can specify on the Binding object, if the data source implements the IDataErrorInfo interface. This new feature enables some of the scenario the previous Custom ValidationRule cannot enable.

One of the scenario the custom ValidationRule cannot support is to enable data binding on the properties of the Custom ValidationRule class. Because ValidationRule is not a DependencyOject, you cannot define dependency properties on it to enable data binding. And because ValidationRule is not a part of the element tree, any Binding expression which relies on RelativeSource or ElementName or similar things that needs to walk up the element tree to find the binding source cannot be evaluated successfully. Some community members such as Josh Smith has come up with a hackery to workaround this limitation using the trick he calls "Virutal Branches". Or our beloved Dr. WPF's ObjectReference custom Markup Extension as he "bloated" in this MSDN WPF thread.

DataErrorValidationRule drives those "dirty tricks" to obsolete. Because You don't need to bind some values to custom ValidationRule object as validation input parameters. Because when implementing IDataErrorInfo, you do the validation logic at the source object. The following is an example of how to leverage the DataErrorValidationRule API to suppport the type of scenario "Virtual Branches" is trying to enable.

First off, the data source class should implement IDataErrorInfo or INotifyPropertyChanged if you want to enable two way data binding as follows:

public class Person : IDataErrorInfo, INotifyPropertyChanged
{
    private int age;
    private int min = 0;
    private int max = 150;

    public int Age
    {
        get { return age; }
        set
        {
            age = value;
            RaisePropertyChanged("Age");
        }
    }

    public string Error
    {
        get
        {
            return null;
        }
    }

    public int Min
    {
        get
        {
            return min;
        }
        set
        {
            min = value;
            RaisePropertyChanged("Min");
        }
    }

    public int Max
    {
        get
        {
            return max;
        }
        set
        {
            max = value;
            RaisePropertyChanged("Max");
        }
    }

    public string this[string name]
    {
        get
        {
            string result = null;

            if (name == "Age")
            {
                if (this.age < this.Min || this.age > this.Max)
                {
                    result = String.Format("Age must not be less than {0} or greater than {1}.", this.Min, this.Max);
                }
            }
            return result;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

You can see that Min and Max properties needs to be specified by the user, so we need to bind those two properties to corresponding UI elements as following XAML snippet shows:

<Window x:Class="BusinessLayerValidation.Window1"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="WPF IDataErrorInfo Sample"
       Width="450" Height="170"
       xmlns:src="clr-namespace:BusinessLayerValidation">

  <
Window.Resources>
    <
src:Person x:Key="data"/>
    <
Style x:Key="textBoxInError" TargetType="TextBox">
      <
Style.Triggers>
        <
Trigger Property="Validation.HasError" Value="true">
          <
Setter Property="ToolTip"
                 Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}
"/>
        </
Trigger>
      </
Style.Triggers>
    </
Style>
  </
Window.Resources>

  <
StackPanel Margin="20" DataContext="{Binding Source={StaticResource data}}">
    <
StackPanel Orientation="Horizontal">
      <
TextBlock Width="60">
        Min:(<TextBlock Text="{Binding Path=Value, ElementName=minSlider}"/>)
      </TextBlock>
      <
Slider Margin="10, 0, 0, 0"
              Name="minSlider"
              Width="300"
              Orientation="Horizontal"
              IsSnapToTickEnabled="True"
              HorizontalAlignment="Right"
              TickPlacement="BottomRight"
              AutoToolTipPlacement="BottomRight"
              Value="{Binding Path=Min, Mode=TwoWay}"
              Minimum="0"
              Maximum="150"
              TickFrequency="10"/>
    </
StackPanel>
    <
StackPanel Orientation="Horizontal">
      <
TextBlock Width="60">
        Max:(<TextBlock Text="{Binding Path=Value, ElementName=maxSlider}"/>)
      </TextBlock>
      <
Slider Margin="10, 0, 0, 0"
              Name="maxSlider"
              Width="300"
              Orientation="Horizontal"
              IsSnapToTickEnabled="True"
              HorizontalAlignment="Right"
              TickPlacement="BottomRight"
              AutoToolTipPlacement="BottomRight"
              Value="{Binding Path=Max, Mode=TwoWay}"
              Minimum="0"
              Maximum="150"
              TickFrequency="10"/>
    </
StackPanel>
    <
TextBlock>Enter your age:</TextBlock>
    <
TextBox Style="{StaticResource textBoxInError}" Name="textBox">
      <
TextBox.Text>
        <
Binding Path="Age"
                ValidatesOnDataErrors="True"
                UpdateSourceTrigger="PropertyChanged">
          <
Binding.ValidationRules>
            <
ExceptionValidationRule/>
          </
Binding.ValidationRules>
        </
Binding>
      </
TextBox.Text>
    </
TextBox>
  </
StackPanel>
</
Window>

You can see from the XAML shown above that DataErrorValidationRule actually provide a greater flexibility when validating data. For a detailed introduction to DataErrorValidationRule, and its role in the WPF data validation model, you can refer to this WPF SDK blog article.

For completeness, I've attached full sample project here for further reference.

Attachment: WPFDataValidation.zip