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 };
        }
    }
}

Zoom manager – A Visual Studio Extension

This extension makes the zoom level consistent in all the editors, and saves it for later to use as your default zoom level.
You don’t have to manage the zoom any more, this extension does it for you.
(it was heavily inspired by the Presentation Zoom extension, which lack the feature of saving)

here it is on the Visual Studio gallery: Zoom Manager

you can also get within your Visual Studio –> Tools –> Extension Manager.

WPF MVVM project template

This extension will add a Visual Studio 2010 project template to create a WPF client application with MVVM pattern project structure.

(after installing the .vsix you’ll need to restart Visual Studio).

the new project template can be found under: File -> New -> Project -> C# templates.
it is called “WPF MVVM Application”.

the new project have an MVVM folders and classes structure with some randomly generated data and a simple view.

download from HERE

C# random helper class

Very often i make an ‘offline’ application in order to work faster without the need to compile the whole big-mama application.
as it name suggest an ‘offline’ app does not have a database to work with so i need to mock some data.
with some code borrowed from this blog post the below C# random helper class can be used in any application.
(if you have something to add drop a comment)

using System;
using System.Text;
using System.Windows.Media;

namespace WpfApplication1.Helper
{
    public static class RandomHelper
    {
        private static Random randomSeed = new Random();

        /// <summary>
        /// Generates a random string with the given length
        /// </summary>
        /// <param name="size">Size of the string</param>
        /// <param name="lowerCase">If true, generate lowercase string</param>
        /// <returns>Random string</returns>
        public static string RandomString(int size, bool lowerCase)
        {
            // StringBuilder is faster than using strings (+=)
            StringBuilder RandStr = new StringBuilder(size);

            // Ascii start position (65 = A / 97 = a)
            int Start = (lowerCase) ? 97 : 65;

            // Add random chars
            for (int i = 0; i < size; i++)
                RandStr.Append((char)(26 * randomSeed.NextDouble() + Start));

            return RandStr.ToString();
        }

        public static int RandomInt(int min, int max)
        {
            return randomSeed.Next(min, max);
        }

        public static double RandomDouble()
        {
            return randomSeed.NextDouble();
        }

        public static double RandomNumber(int min, int max, int digits)
        {
            return Math.Round(randomSeed.Next(min, max - 1) + randomSeed.NextDouble(), digits);
        }

        public static bool RandomBool()
        {
            return (randomSeed.NextDouble() > 0.5);
        }

        public static DateTime RandomDate()
        {
            return RandomDate(new DateTime(1900, 1, 1), DateTime.Now);
        }

        public static DateTime RandomDate(DateTime from, DateTime to)
        {
            TimeSpan range = new TimeSpan(to.Ticks - from.Ticks);
            return from + new TimeSpan((long)(range.Ticks * randomSeed.NextDouble()));
        }

        public static Color RandomColor()
        {
            return Color.FromRgb((byte)randomSeed.Next(255), (byte)randomSeed.Next(255), (byte)randomSeed.Next(255));
        }

    }
}
Follow

Get every new post delivered to your Inbox.