Silverlight: TreeView Scroll behavior

The tree control (TreeView) of Silverlight have a problem with scrolling – it scroll only when the mouse is over an item. if you try to scroll when the mouse is over an empty space… nothing will happen.
see this stackoverflow issue.

i came up with a simple solution that do not require any intervention in templates.
a simple behavior for your misbehaving tree view:

public class TreeScrollBehavior : Behavior<TreeView>
{

    #region ScrollDelta (DependencyProperty)

    public double ScrollDelta
    {
        get { return (double)GetValue(ScrollDeltaProperty); }
        set { SetValue(ScrollDeltaProperty, value); }
    }
    public static readonly DependencyProperty ScrollDeltaProperty =
        DependencyProperty.Register("ScrollDelta", typeof(double), typeof(TreeScrollBehavior),
            new PropertyMetadata(20.0)
            );

    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.AddHandler(UIElement.MouseWheelEvent, new MouseWheelEventHandler(OnMouseWheel), true);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.MouseWheel -= OnMouseWheel;
    }

    private void OnMouseWheel(object sender, MouseWheelEventArgs e)
    {
        e.Handled = true;
        var delta = ScrollDelta;
        if (e.Delta > 0)
            delta = 0 - ScrollDelta;
        var scroll = AssociatedObject.GetScrollHost();
        scroll.ScrollToVerticalOffset(scroll.VerticalOffset + delta);
    }
}

example use:

<sdk:TreeView>
    <i:Interaction.Behaviors>
        <shemesh:TreeScrollBehavior ScrollDelta="50"/>
    </i:Interaction.Behaviors>
</sdk:TreeView>

Silverlight: right-click ContextMenu for a DataGrid

It required some digging to get it right, all the solutions I’ve found by googling were insufficient.
Here are the problems I had to solve:
- Adding the ContextMenu on the DataGrid made it work on the headers as well, I wanted it only on the ‘body’, excluding headers.
- Adding the ContextMenu of each row using the LoadingRow event involved making lots new instances of the menu, and it raised some thinking about performance with large data and virtualization.
- I wanted it to be simple to use, easy to add commands etc. simply as we adding a ContextMenu elsewhere.
- Should be used in XAML, not code.

Parts of my code is borrowed from Craig Wardman blog, thanks.

Here is the code, and down below is how you use it:

public class DataGridContextMenuBehavior : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        if (ContextMenu != null) AssociatedObject.LayoutUpdated += AssociatedObjectOnLayoutUpdated;
        AssociatedObject.AddHandler(UIElement.MouseRightButtonDownEvent,
            new MouseButtonEventHandler(OnMouseRightButtonDown), true);
    }

    private void AssociatedObjectOnLayoutUpdated(object sender, EventArgs eventArgs)
    {
        if(ContextMenu == null) return;
        var rowsPresenter = FindChildControlByType<DataGridRowsPresenter>(AssociatedObject);

        if (rowsPresenter != null)
        {
            ContextMenuService.SetContextMenu(rowsPresenter, ContextMenu);
            AssociatedObject.LayoutUpdated -= AssociatedObjectOnLayoutUpdated;
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.MouseRightButtonDown -= OnMouseRightButtonDown;
    }

    private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        var elementsUnderMouse = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null),
            AssociatedObject);

        var row = elementsUnderMouse.OfType<DataGridRow>().FirstOrDefault();

        if (row != null)
            AssociatedObject.SelectedItem = row.DataContext;
    }

    #region ContextMenu (DependencyProperty)

    public ContextMenu ContextMenu
    {
        get { return (ContextMenu)GetValue(ContextMenuProperty); }
        set { SetValue(ContextMenuProperty, value); }
    }
    public static readonly DependencyProperty ContextMenuProperty =
        DependencyProperty.Register("ContextMenu", typeof(ContextMenu), typeof(DataGridContextMenuBehavior),
            new PropertyMetadata(null)
            );

    #endregion

    #region find child control by type http://www.craigwardman.com/blog/index.php/2011/06/finding-a-child-control-by-type-in-silverlight/

    private static T FindChildControlByType<T>(DependencyObject container) where T : DependencyObject
    {
        return FindChildControlByType<T>(container, null);
    }

    private static T FindChildControlByType<T>(DependencyObject container, string name) where T : DependencyObject
    {
        T foundControl = null;

        //for each child object in the container
        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
        {
            //is the object of the type we are looking for?
            if (VisualTreeHelper.GetChild(container, i) is T &&
                (VisualTreeHelper.GetChild(container, i).GetValue(FrameworkElement.NameProperty).Equals(name) ||
                    name == null))
            {
                foundControl = (T) VisualTreeHelper.GetChild(container, i);
                break;
            }
            //if not, does it have children?
            if (VisualTreeHelper.GetChildrenCount(VisualTreeHelper.GetChild(container, i)) > 0)
            {
                //recursively look at its children
                foundControl = FindChildControlByType<T>(VisualTreeHelper.GetChild(container, i), name);
                if (foundControl != null)
                    break;
            }
        }

        return foundControl;
    }

    #endregion
}

example of use:

<sdk:DataGrid Name="DataGrid1"
                ItemsSource="{Binding SourceCollection}">
    <i:Interaction.Behaviors>
        <shemesh:DataGridContextMenuBehavior>
            <shemesh:DataGridContextMenuBehavior.ContextMenu>
                <toolkit:ContextMenu>
                    <toolkit:MenuItem Header="Cut"
                                        Click="MenuItem_OnCut" />
                    <toolkit:MenuItem Header="Copy"
                                        Command="{Binding OnCopyCommand}" />
                </toolkit:ContextMenu>
            </shemesh:DataGridContextMenuBehavior.ContextMenu>
        </shemesh:DataGridContextMenuBehavior>
    </i:Interaction.Behaviors>
</sdk:DataGrid>

Silverlight: Search box

A simple search box for Silverlight.
it got a watermark, a search button, and a “clear search” button.

pretty simple and basic.

Here is the demo
and the code is on GitHub, you can browse (and get) it here.

Silverlight: close Popup on click outside

This was a hard one…
One of the things that is very much missing in Silverlight is closing a Popup upon clicking outside of it.
this is implemented as StaysOpen (true/false) in WPF.
but is very much missing in Silverlight Popup.

i wanted to make it as simple as possible, without any dependencies on other dll, and support binding changes.
so an attached property was the way to go.

lots of thanks to Vladi (koganvladimir at yahoo dot com) on helping here.

to use it simply add the attached property to your Popup, when the value is set to False the popup will be closed upon clicking outside.
here are two common use case examples:

<Popup helpers:PopupHelper.StaysOpen="False">
<Popup helpers:PopupHelper.StaysOpen="{Binding SomeChangingBool}">

and here is the PopupHelper.cs class:

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

namespace Helpers
{
    public class PopupHelper
    {

        public static bool GetStaysOpen(DependencyObject obj)
        {
            return (bool)obj.GetValue(StaysOpenProperty);
        }

        public static void SetStaysOpen(DependencyObject obj, bool value)
        {
            obj.SetValue(StaysOpenProperty, value);
        }

        public static readonly DependencyProperty StaysOpenProperty =
            DependencyProperty.RegisterAttached("StaysOpen", typeof(bool), typeof(PopupHelper),
                                                new PropertyMetadata(true, StaysOpenChanged));

        private static void StaysOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var pop = d as Popup;
            if (pop == null) return;

            // this is the only way i could find to tell (or wait) for after loaded.
            if (pop.Child == null)
            {
                pop.Loaded += PopOnLoaded;
            }
            else
            {
                UpdateStaysOpen(pop, (bool)e.NewValue);
            }
        }

        private static void PopOnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            var pop = sender as Popup;
            if (pop == null) return;
            pop.Loaded -= PopOnLoaded;
            UpdateStaysOpen(pop, (bool)pop.GetValue(StaysOpenProperty));
        }

        private static void UpdateStaysOpen(Popup popup, bool stayOpen)
        {
            var blocker = GetBlocker(popup);
            if (blocker == null)
            {
                SetBlockerLayer(popup);
                blocker = GetBlocker(popup);
            }
            blocker.IsHitTestVisible = !stayOpen;
        }

        private static Canvas GetBlocker(Popup pop)
        {
            var elementPopupChildCanvas = pop.Child as FrameworkElement;
            var blocker = VisualTreeHelper.GetChild(elementPopupChildCanvas, 0) as FrameworkElement;
            Canvas retVal;
            if ((retVal = blocker as Canvas) != null && blocker.Tag.ToString() == "ElementPopupBlocker")
            {
                return retVal;
            }
            return null;
        }

        private static void SetBlockerLayer(Popup popup)
        {
            var popupChild = popup.Child as FrameworkElement;
            if (popupChild == null) return;
            var blocker = new Canvas { Background = new SolidColorBrush(Colors.Gray), Tag = "ElementPopupBlocker" };
            blocker.MouseLeftButtonDown += delegate { popup.IsOpen = false; };
            var elementPopupChildCanvas = new Canvas();
            popup.Child = elementPopupChildCanvas;
            elementPopupChildCanvas.Children.Add(blocker);
            elementPopupChildCanvas.Children.Add(popupChild);

            popupChild.HorizontalAlignment = HorizontalAlignment.Left;
            popupChild.VerticalAlignment = VerticalAlignment.Top;
            Canvas.SetLeft(popupChild, popup.HorizontalOffset);
            Canvas.SetTop(popupChild, popup.VerticalOffset);
            popup.HorizontalOffset = 0.0;
            popup.VerticalOffset = 0.0;

            popup.LayoutUpdated += delegate { Arrange(popup); };
        }

        private static void Arrange(Popup popup)
        {
            if (!popup.IsOpen) return;
            var elementPopupChildCanvas = popup.Child;
            if (elementPopupChildCanvas == null) return;
            var blocker = VisualTreeHelper.GetChild(elementPopupChildCanvas, 0) as Canvas;
            var popupChild = VisualTreeHelper.GetChild(elementPopupChildCanvas, 1) as FrameworkElement;
            if (blocker == null || popupChild == null) return;

            var width = Application.Current.Host.Content.ActualWidth;
            var height = Application.Current.Host.Content.ActualHeight;
            if (height < 50.0 || width < 50.0)
                return;

            var generalTransform = popup.TransformToVisual(null);
            var point1 = new Point(0.0, 0.0);
            var point2 = new Point(1.0, 0.0);
            var point3 = new Point(0.0, 1.0);
            var point4 = generalTransform.Transform(point1);
            var point5 = generalTransform.Transform(point2);
            var point6 = generalTransform.Transform(point3);

            var identity = Matrix.Identity;
            identity.M11 = point5.X - point4.X;
            identity.M12 = point5.Y - point4.Y;
            identity.M21 = point6.X - point4.X;
            identity.M22 = point6.Y - point4.Y;
            identity.OffsetX = point4.X;
            identity.OffsetY = point4.Y;
            var num = identity.M11 * identity.M22 - identity.M12 * identity.M21;
            var matrix = identity;
            identity.M11 = matrix.M22 / num;
            identity.M12 = -1.0 * matrix.M12 / num;
            identity.M21 = -1.0 * matrix.M21 / num;
            identity.M22 = matrix.M11 / num;
            identity.OffsetX = (matrix.OffsetY * matrix.M21 - matrix.OffsetX * matrix.M22) / num;
            identity.OffsetY = (matrix.OffsetX * matrix.M12 - matrix.OffsetY * matrix.M11) / num;

            blocker.Width = width;
            blocker.Height = height;
            blocker.RenderTransform = new MatrixTransform { Matrix = identity };
        }
    }
}

Resource Viewer V2.0

Version 2.0 is out!

Read the first post and full description: Resource Viewer – A Visual Studio Extension

New in V2.0:

  • Added support for additional resource types – now you can also view your Brush(es):DrwaingBrush,  SolidColorBrush, Gradients, etc.
  • Details view.
  • The code is now constructed better.

The whole project and source code is maintained here: http://resourceviewer.codeplex.com/

It is also hosted in the Visual Studio gallery, here: Resource Viewer

Resource Viewer – A Visual Studio Extension

Simply put the “resource viewer extension” enables you to visually view your ResourceDictionary.

To open it go to: View – Other Windows – Resource Viewer.

When working with WPF/Silverlight you put your reusable resources in a common ResourceDictionary, those resources might be of type Style, SolidColorBrush, DrawingBrush, BitmapImage and more. The problems starts when you have that ResourceDictionary you have no way to see how your resources look like, making the work process (of both the developer who maintains this dictionary and the one who uses it) clumsy and based on guessing.

This extension comes to solve those problems!

With the “Resource viewer “ extension you can open any ResourceDictionary and actually see how your resources look like – your icons, brushes, etc. are all being visualized right inside Visual Studio.

Than when you’ve found the resource you’ve been after you can “inject” it into a file that is opened within Visual Studio.

Features:

  • A Visual Studio 2010 extension – meaning it integrates with VS api and behave like any other window (docking, floating etc.)
  • Open any XAML ResourceDictionary file on your computer – simply browse to it.
  • When you open the viewer it automatically opens the last opened file.
  • Most recently used files list.
  • Refresh button to reload the opened file.
  • The opened file is being watched for changes, if a change is made you’ll be notified.
  • Zoom slider and zoom presets.
  • Search box.
  • Inject the selected item’s x:Key into an opened document by double-click on an item or pressing the “Inject” button.
  • Inject prefix and suffix strings. Those are saved and persisted.

Future plans:

  • Current version handles only DrawingImage, add handling for more resource types.
  • Add handling for Merged Dictionaries.
  • Add a detailed view for the selected item.
  • More…

If you are willing to help in any way please drop me a note.

The whole project and source code is maintained here: http://resourceviewer.codeplex.com/

It is also hosted in the Visual Studio gallery, here: Resource Viewer

Follow

Get every new post delivered to your Inbox.