... a journey through WPF, MVVM and .NET4 RSS 2.0
# Thursday, October 02, 2008
Here are the demos for the guys who attended the 2-day binding + network Silverlight2 training in Boston.  Thanks guys!


Thursday, October 02, 2008 9:04:48 AM (Central Daylight Time, UTC-05:00)  #    Comments [0] -

# Monday, September 22, 2008
Thanks to all who attended - it was great to meet all of you and hang out until the wee hours and play Rock Band! 

Here are the demos from the class - I removed most of the binaries to bring it down to a reasonable size.

I also put the pictures online at Flickr

Thanks again everyone!


Monday, September 22, 2008 8:33:32 AM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | Code
# Friday, September 12, 2008

One thing that's kind of cool in WPF is that you can use brushes to fill almost anything and that there are some really cool brushes in the toolkit.  It always blows people away when you show them a piece of text filled with a live video complements of a Visual Brush.

 

But, unfortunately text doesn't have a stroke property - only a fill so you can't add an edge to it.  You can layer another piece of text under it, but often it doesn't quite match up size-wise when you do this.  The solution is to convert the text to a Path which has both a fill and stroke and it turns out it's pretty easy to do.

 

If you just have one piece of text you are better off using Blend's "Convert to Path" option -- it will do the one-time conversion for you and you can just insert the shape into your UI.  If you have more than one element though it can be tedious, and it doesn't work at all for dynamic pieces of text - that's where this TextPath class comes in handy.   With it you can create text like this:

 

 

Notice how the text is outlined in a different color -- any brush could be used so you could do wacky things like have the "WPF" part be ringed in fire (using the Dreamscene fire video for example). 

 

The class is dead simple to use, it's a Shape class so you can simply insert it right into your XAML and set the font properties and text:


 

<StackPanel Orientation="Horizontal" TextElement.FontWeight="Bold" TextElement.FontSize="72pt">
       
<me:TextPath FontFamily="Consolas,Courier New" Margin="5" Text="WPF"
                      
StrokeThickness="3" Fill="Gold" Stroke="Red" /> 
        
<me:TextPath FontFamily="Balloon" Margin="5" Text="Rocks" StrokeThickness="3" Stroke="Black">
          
<me:TextPath.Fill>
             
<ImageBrush ImageSource="rocks.jpg" />
           
</me:TextPath.Fill>
       
</me:TextPath>
</StackPanel>

 

Notice it uses the same font dependency properties as all other text-based framework elements - that allows us to inherit the property values which is useful. 

 

Here's the sample project which full source code:  OutlineTextTest.zip (1.54 MB)

Friday, September 12, 2008 10:52:21 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF

We've seen how to programatically control focus and that's all great stuff, but one thing I like to do with WPF is see how much of the repetitive or UI-specific code I can move into the XAML and keep out of the code behind.  We can use the FocusManager.FocusedElement property to shift focus in XAML but it only works when the element exists in the main XAML file.  If you use UserControls it turns out that the approach doesn't work because that element is loaded separately and not available when the initial focus is being determined.

In my specific case, I have a wizard-style application which utilizes a TabControl to move between the pages.  The first page looks like this:

The XAML layout for window1.xaml looks something like this:

<TabControl x:Name="Pages" SelectedIndex="0">
   <FocusTest:Page1 />
   <FocusTest:Page2 />
   <FocusTest:FinalPage />
</TabControl>

What I want is to have focus immediately positioned within the first text box but it turns out that you can't get WPF to do this directly from XAML - because the TextBox isn't a direct descendent of Window - it's part of the Page1 user control.  You can try putting the focus shift into the user control but it turns out that it won't work there either - it's got to be assigned to the focus scope which is the window.

So it might seem we are stuck with adding code behind logic (blech!) but all is not lost!  When I hit situations like this, I try to think about how to solve the problem generically so I can reuse my solution.  In this case, I decided to build a MarkupExtension to locate the first focusable element descendent.

If you are not familiar with markup extensions, they are a corner piece in the XAML extensibility story.  They allow for dynamic property assignment - where the value is determined at runtime vs. XAML compile time.  That's exactly what I need here - I want to find that TextBox at runtime and shift focus to it - just like I would have done in the code behind.

Creating a markup extension is trivial - you just extend the MarkupExtension base class and implement the ProvideValue method.  In this case, when provide value is called, I have to do several steps:

  1. If the element we are bound to is not loaded yet, we need to wait for it.  We won't be able to find the child in the visual tree if the parent isn't yet loaded.
  2. Once it is loaded, search the children and find the first control we can give focus to.  That means, the control is visible, focusable and enabled.
  3. Assign logical focus to the new control in the closest focus scope parent, or just return the value if the property being assigned to is not FocusManager.FocusedElement.

I decided to try to make a generic markup extension that could be used outside my scenario so I added a little bit of code to see if we are assigning to FocusManager.FocusedElement and act differently in that specific case - otherwise the extension just returns the value.

With this new extension, I can now add a single line of code to each user control:

<UserControl x:Class="FocusTest.Page2"
   xmlns:FocusTest="clr-namespace:FocusTest"
   FocusManager.FocusedElement="{FocusTest:FirstFocusedElement}">

Now, when I run the application, focus is always assigned to the first control it can be assigned to.  It turns out that I can go one step better and always assign focus when the page becomes visible in my wizard - remember we have to wait for the control to finish loading to find the element.. this is done by hooking the FrameworkElement.Loaded event.  This event is raised each time the TabControl shifts to a new tab - so if we never unhook our handler, our focus management code is called each time the tab page becomes visible.  This might not be the required behavior so I added a simple property to the markup extension called OneTime to control that behavior.

There's a bit too much code to blog here, but if you want to try this out yourself, here's the test project.. feel free to use this however you like.  FocusTest.zip (17.88 KB)

Update: Andrew Smith pointed out that the code would assign focus to a focus scope if it ran across one - which could happen if you have a toolbar or menu present in the UI.  I didn't in the sample (or in my production app) but I could certainly see that being a common reality.  I changed the above code to deliberately skip focus scopes and their children and keep going down the tree until it finds the first child.  Thanks Andrew!

Friday, September 12, 2008 7:49:20 AM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Thursday, September 04, 2008

In the last post, I wrote about how focus is generally managed in WPF - we have focus scopes to track a single element within that scope for logical focus, and then one of those elements is given physical, or keyboard focus.

Now, let's talk a little about how you can influence that programatically.  First, you can always determine which element has logical focus in your application through the FocusManager.GetFocusedElement method -- pass it the window in question and it will return which element has logical focus in that window.  Remember that logical focus != keyboard focus at all times -- toolbars and menus track their own focus so if you are currently interacting with a menu then the menu has physical focus.  But in general, the following code will tell you which element WPF thinks has focus in the window:

IInputElement focusedElement = FocusManager.GetFocusedElement(thisWindow);

To determine whether this element has keyboard focus, we can check the IsKeyboardFocused property - if it's set to true, then that element currently has the keyboard focus (as well as being the logical focus for that focus scope).

Keyboard focus is most often set through runtime activity - the user clicks on an element, or uses the TAB key to move around the UI.  You can also set it programatically a couple of ways.  First, there is a Keyboard class in WPF which exposes several methods and properties.  There is a Keyboard.FocusedElement read-only property which returns the current keyboard focused element, and there is a Keyboard.Focus method which attempts to change keyboard focus.  It returns the element that now has focus - so you can check to see if your request was fulfilled or ignored.  So, for example, you can change focus during your application initialization:

void OnLoaded(object sender, RoutedEventArgs e)
{
   Keyboard.Focus(firstTextBox);
}

Notice that we uses the Loaded event - this is because no focus requests will be accepted prior to the element being initialized and loaded.  That's the first place in the application where you can make focus changes.

When would setting focus fail?  Well, it can fail for a lot of reasons, but the most common are:

  1. The element has Focusable = false
  2. The element has Enabled = false
  3. The element has IsVisible = false
  4. The element has not been loaded yet
  5. The currently focused element will not release focus.

That final one is important, changing focus involves potentially taking it away from an existing element - they receive a PreviewLostKeyboardFocus and LostKeyboardFocus event.  If they handle the preview event, focus will not change.

You can also manipulate focus through programatic keyboard navigation - simulating the user pressing TAB to cycle through the focusable elements.  This is controlled through the KeyboardNavigation class which is used when the user presses a key that changes focus (TAB, SHIFT+TAB, Up, Down, etc.).

Controls can set a TabIndex property assignment which determines the tabbing order.  The default is to tab through them in order of declaration.  You can also use the KeyboardNavigation.TabIndex attached property which works for any element - not just controls.

To control navigation, the KeyboardNavigation class has an attached property TabNavigation allowing you to change how navigation occurs within a container.  You can set it to:

  1. Continue - each focusable element receives focus and the container is exited when the edge is reached.
  2. Cycle - focus does not leave the container but wraps around the edges
  3. Once - the container itself is treated as a single focusable element where only the first child receives focus
  4. Local - uses TabIndex locally within the container - independant of any outside elements.
  5. Contained - focus statys in the container but does not wrap (stays at edges when top/bottom are reached)
  6. None - no keyboard navigation allowed in the container

The default is Continue, but you can set the attached property on any element to change it for that element and any children. To see this in action, paste the following into your XAML editor of choice and change the ComboBox while tabbing through the TextBlock elements.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="Simple Focus"
>

<Window.Resources>
   <Style TargetType="TextBox">
      <Setter Property="Margin" Value="10" />
      <Setter Property="Width" Value="100" />
   </Style>
</Window.Resources>

<StackPanel>
   <ComboBox x:Name="tabStyles" SelectedIndex="0" Focusable="False">
      <sys:String>None</sys:String>
      <sys:String>Continue</sys:String>
      <sys:String>Cycle</sys:String>
      <sys:String>Once</sys:String>
      <sys:String>Local</sys:String>
      <sys:String>Contained</sys:String>
   </ComboBox>

   <TextBox TabIndex="1" />
   <TextBox TabIndex="2" />
   <StackPanel
       
KeyboardNavigation.TabNavigation="{Binding ElementName=tabStyles,Path=SelectedItem}">
      <TextBox TabIndex="1" />
      <TextBox TabIndex="2" />
   </StackPanel>
</StackPanel>
</Window>

You can have the system ignore specific elements (but still allow them to have focus) by setting the KeyboardNavigation.IsTabStop="false" attached property.  This will cause keyboard navigation to "jump" over the control as if it were not present.

Three methods are exposed by UIElement and FrameworkElement to programatically shift focus: Focus, MoveFocus and PredictFocus.

To force focus to a specific element, you can call Focus on it.  For example, above we set the keyboard focus by calling Keyboard.Focus(), but the same effect can be achieved like this:

void OnLoaded(object sender, RoutedEventArgs e)
{
   firstTextBox.Focus();
}

This method attempts to set focus using Keyboard.Focus().  If that fails, but the element is Focusable and enabled, it finds the focus scope for the element and sets logical focus there (so that keyboard focus will eventually end up on the control).

FrameworkElement.MoveFocus is used to change the keyboard focus in the application using the same algorithm as the TAB traversal.  You pass in the direction (specified through a TraversalRequest object) and the method returns true/false to indicate success.   Under the covers it actually uses the KeyboardNavigation class, but it's an easy way to push focus around the window:

firstTextBox.MoveFocus(
           
new TraversalRequest(FocusNavigationDirection.Next));

PredictFocus works the same way, but instead of actually shifting focus, it returns what would be the focused item if you were to execute MoveFocus.

So, up to this point, we've seen a lot of code to change focus.  However, the most common request is to set initial focus to a specific control - remember that WPF doesn't do that by default.  You can do it in code, just like the above example where we use the Loaded event.  Or, it turns out you can do it in XAML too.  The key to remember is that the FocusedElement of the main focus scope (the Window) is the one that will get initial focus.  That is (by default) null, but you can set it in XAML using the attached property syntax.  Using the above XAML example, we can supply a name for one of the TextBox controls and then a little data binding magic to set that onto the Window:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="Simple Focus"
        FocusManager.FocusedElement="{Binding ElementName=tb2}">
  ...
    <TextBox TabIndex="1" />
    <TextBox x:Name="tb2" TabIndex="2" />
  ...
</Window>

Now when you run the application, focus is placed into the second text box in the window.  This technique works great as long as the element you want to assign focus to is declared here in the same XAML file.  However, a popular way to develop WPF applications is to separate out chunks of UI into separate UserControls.  When you do that, the above trick fails -- even if you put the FocusManager.FocusedElement binding into the UserControl!

How we solve that is what we'll look at in the next post!  Stay tuned...

 

 


Thursday, September 04, 2008 3:44:44 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Tuesday, September 02, 2008

Focus Types

As you may know, in WPF there are two types of focus: logical focus and keyboard focus. 

Keyboard focus is the easiest to grasp: the element with keyboard focus is where any keystrokes will end up.  Only one element can have keyboard focus at any particular time, and it’s possible that no element currently has keyboard focus (this happens most commonly when the application itself is not the activated application).

Logical focus is a little murkier.  Logical focus represents where keyboard focus could go within a group of elements.  There may be multiple elements with logical focus within the application and one of them likely has keyboard focus.  Having keyboard focus automtically indicates logical focus but not vice-versa.

This distinction is modeled after Win32 itself -- each thread has one HWND it has identified to have focus but only one of them really has input focus at any given point in time.

In WPF, logical focus is tracked and managed by a Focus Scope.  A focus scope is created by certain elements to keep track of which element should have focus.  There can be multiple focus scopes within the application – each identifying a single element that has logical focus.   Focus scopes are created and managed by the FocusManager class.  It exposes two attached properties and the requisite static method wrappers to access them:

IsFocusScope - determines whether the current object is a focus scope.
FocusedElement - returns which element child of the given focus scope object has logical focus.

It also exposes a handy method to find the focus scope for a given element - GetFocusScope

So what creates a focus scope and why do we need more than one?  Well, the elements in WPF that create focus scopes by default are Window,  Menu, ContextMenu and ToolBar.  The reason we need more than one is to ensure your application works the way you expect it to.  When you are typing in a TextBox and click a menu item, you really want focus to return to the original TextBox once the menu is dismissed.  That’s focus scopes in action – the Window maintains logical focus in the TextBox and when you click on the menu, keyboard focus shifts to the menu.  Since it maintains its own focus scope, it has its own logically focused element that WPF sets focus to (the menu and menu items in this case).  Once you shift back to the window, keyboard focus moves back to that focus scope’s logical focus: the text box.  Without focus scopes to track the original focus holder, WPF wouldn't know where focus should go.

Focus scopes are also critical to command routing – often execution of commands depends upon focus.  Some commands become active because a specific control which has a handler for the command has focus.  Without focus scopes, we could not have menu items and toolbar buttons initiate those commands – they would steal focus away from the target, making the command unavailable.  However, when WPF encounters a focus scope it checks the element that has logical focus in that scope to see if it can handle the command.  If not, the command continues routing up to the parent of the focus scope.

To show all of this behavior, I have rigged up a sample application with two windows.  The first is a traditional text editor window with a menu and toolbar and a RichEdit control for the content.  It looks like:

ste001.jpg

Forgive my 5-minute graphics for the buttons – I just threw them together in Visual Studio, a real project would use Blend to generate the graphics. 

Regardless of it's look, the application functions the way you expect – you can type in the text field, click buttons and select menu choices to Open, Cut, Copy and Paste content.  The Cut/Copy/Paste commands are only available when the TextBox is in the appropriate state:

Cut:       TextBox has focus and has some text selected.
Copy:    TextBox has focus and has some text selected.
Paste:    TextBox has focus and text exists on the clipboard.

This is all an artifact of the routed command system in WPF – the TextBox has command handlers registered for ApplicationCommands.Cut, ApplicationCommands.Copy and ApplicationCommands.Paste and when it has focus (and the above criteria is met) those commands could be executed.

When you click on a button or a menu choice, it executes the command it is associated with and WPF decides which handler should be called.  This normally involves walking the visual tree:

ste002.jpg

In this case, if the menu item for “Cut” were selected, it would start looking at the menu, then move up the tree consulting each parent and looking for a CommandBinding to handle the command.  If that were the whole story then the Cut command would never happen because nothing in the visual tree above the menu has a binding to execute the Cut command.  But, of course,  it’s not the whole story – in this case, WPF sees that the Window is a focus scope and so it gets the logically focused element from the Window and looks for a CommandBinding there.  That happens to be our RichTextBox – which is where the command ultimately gets handled. 

To see all of this in action, the second part of the application is a focus scope monitor window. 

ste004.jpg

It shows all the known focus scopes and what the active focused element is, as well as whether that element has keyboard focus.  The window is live so as you click around in the text editor you can see focus shifting and changing.  The element with keyboard focus is the Paste menu item – notice that the rich text box is the logically focused item for “Window1” but does not have keyboard focus.  Once you select the menu choice (or cancel) then keyboard focus shifts back to Window1 which assigns it to the RichTextBox.

ste003.jpg

If you'd like to play with this sample, you can get the full source code here.

In the next blog post, I’ll write about how you can programmatically assign and control focus in code and XAML.

Tuesday, September 02, 2008 2:55:39 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Monday, September 01, 2008

Recently I was involved in a project where we needed to build a multi-step input application where each step showed progress and you could proceed forward and backward through the pages.  I looked at the Navigation support in WPF (which is nice) but ultimately decided to model it around a styled TabControl – each page being a tab and the progress noted through the custom TabItem visuals across the top.  It all works great and looks fabulous.

One of the bugs that was logged against the application and got assigned to me was related to focus – the initial keyboard focus was not being placed into the first control, instead it appeared that nothing had focus which is totally accurate.  Those that are new to WPF might be surprised that it does not assign initial focus to any particular child control – you must deliberately click or tab into a control to give it focus.   You can, of course, also assign focus programmatically but it turns out to not be as easy as you’d think.  In fact, I encountered several nasty gotchas trying to get it to work exactly the way I wanted it to. 

My frustrations with focus and how WPF manages it have resulted in this set of blog posts

Part 1:  Focus Basics
Part 2:  Setting focus in code and XAML

Part 3:  Getting a little smarter – setting focus to the first focusable control
Part 4:  Setting focus to template items

Monday, September 01, 2008 2:07:39 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Monday, August 25, 2008
Business validations in WPF
Monday, August 25, 2008 9:45:20 AM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Friday, August 01, 2008

Thanks to all who came - it was fast-paced but a lot of fun to hang with all of you.  Here are the:

demos.zip and labs.zip

Friday, August 01, 2008 6:20:02 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET
# Friday, July 25, 2008
It's been a long time since I blogged anything specific on WPF -- I've been doing a lot of it lately, along with some Silverlight.  Recently I was experimenting with dragging tabs around on a TabControl at runtime.  My end goal is really to implement it with Silverlight 2, but I've found it's much easier to prototype things in WPF and then port them over because the debugging experience is easier with WPF.

I didn't want to create derived implementations of any classes - I wanted something that was non-intrusive to my code so I decided to use an attached property.  Attached properties are basically property values "attached" to a class at runtime - where the property itself isn't defined on the target but instead on some other type.  The cool thing about attached properties is they can register a change notification handler which gives them a reference to the object they are being placed on -- this is how the Spell Checker works with the TextBox in WPF.  All the code for the spell checking lives in the SpellChecker class and when you add the SpellCheck.IsEnabled property onto the TextBox, it adds handlers to the TextBlock's TextChanged property and adds all the nifty spell checking goodness without changing the code in TextBox.

Back to my drag/drop prototype.  So with this code, I can add the property to any TabControl and get a nice, simple drag/drop experience.  It's far from complete - it would be cooler if the tabs moved around as you dragged (they don't), but I was just prototyping here.

Here's the code:




using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApplication1
{
    public class DragDropTabManager
    {
        private static readonly DependencyProperty ManagerProperty =
            DependencyProperty.Register(typeof (DragDropTabManager).ToString(), typeof (DragDropTabManager),
                                        typeof (DragDropTabManager));

        public static readonly DependencyProperty EnabledProperty = 
            DependencyProperty.RegisterAttached("Enabled", typeof(bool), 
                                                typeof(DragDropTabManager),
                                                new PropertyMetadata(false, DDTM_EnabledChanged));

        private static void DDTM_EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tc = d as TabControl;
            if (tc != null)
            {
                var oldValue = (bool) e.OldValue;
                var newValue = (bool) e.NewValue;

                if (oldValue == true && newValue == false)
                {
                    var ddtm = tc.GetValue(ManagerProperty) as DragDropTabManager;
                    if (ddtm != null)
                    {
                        tc.PreviewMouseDown -= ddtm.TabItem_PreviewMouseDown;
                        tc.SetValue(ManagerProperty, null);
                    }
                }
                else if (oldValue == false && newValue == true)
                {
                    var ddtm = new DragDropTabManager();
                    tc.SetValue(ManagerProperty, ddtm);
                    tc.PreviewMouseDown += ddtm.TabItem_PreviewMouseDown;
                }
            }
        }

        public static bool GetEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnabledProperty);
        }

        public static void SetEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(EnabledProperty, value);
        }

        private bool isMoving;
        private TabItem movingTabItem;
        private TabItem lastTab;
        private Point ptStart;

        void TabItem_PreviewMouseDown(object sender, MouseEventArgs e)
        {
            var ti = e.Source as TabItem;
            if (ti != null && e.LeftButton == MouseButtonState.Pressed)
            {
                var tc = ti.Parent as TabControl;
                if (tc != null)
                {
                    tc.MouseMove += tc_MouseMove;
                    tc.MouseLeftButtonUp += tc_MouseLeftButtonUp;

                    ptStart = e.GetPosition(tc);
                    movingTabItem = ti;
                }
            }
        }

        void tc_MouseMove(object sender, MouseEventArgs e)
        {
            var tc = sender as TabControl;
            if (tc == null)
                return;

            Point pt = e.GetPosition(tc);

            if (isMoving == false)
            {
                if (Math.Abs(pt.X - ptStart.X) > 10)
                {
                    movingTabItem.IsHitTestVisible = false;
                    movingTabItem.RenderTransformOrigin = new Point(.5, .5);
                    movingTabItem.RenderTransform = new TranslateTransform(0, 0);
                    tc.Cursor = Cursors.Hand;
                    Panel.SetZIndex(movingTabItem, 1);
                    isMoving = true;
                    tc.CaptureMouse();
                }
                return;
            }

            TabItem newPos = FindTabItem(tc, pt);
            if (newPos == null)
                tc.Cursor = Cursors.No;
            else
            {
                lastTab = newPos;
                var xform = movingTabItem.RenderTransform as TranslateTransform;
                if (xform != null)
                    xform.X = pt.X - ptStart.X;
                tc.Cursor = Cursors.Hand;
            }
        }

        void tc_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var tc = sender as TabControl;
            Debug.Assert(tc != null);

            tc.ReleaseMouseCapture();
            tc.MouseMove -= tc_MouseMove;
            tc.MouseLeftButtonUp -= tc_MouseLeftButtonUp;

            if (isMoving == true)
            {
                isMoving = false;
                tc.Cursor = Cursors.Arrow;
                movingTabItem.RenderTransform = null;
                movingTabItem.IsHitTestVisible = true;

                Panel.SetZIndex(movingTabItem, 0);

                if (lastTab != null)
                {
                    if (lastTab != null && movingTabItem != lastTab)
                    {
                        int targetIndex = tc.Items.IndexOf(lastTab);
                        tc.Items.Remove(movingTabItem);
                        tc.Items.Insert(targetIndex, movingTabItem);

                        movingTabItem.Focus();
                    }
                }
            }
            movingTabItem = lastTab = null;
        }

        private static TabItem FindTabItem(UIElement parent, Point pt)
        {
            var fe = parent.InputHitTest(pt) as FrameworkElement;
            while (fe != null && fe.GetType() != typeof(TabItem))
                fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
            return fe as TabItem;
        }
    }
}
Friday, July 25, 2008 3:09:45 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | Code | WPF
I'm a WPF Disciple
Search
Categories
Archive
<October 2008>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Mark Smith
Sign In
All Content © 2010, Mark Smith
DasBlog theme 'Business' created by Christoph De Baene (delarou)