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>
Advertisements

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

Silverlight: toggle button bar

I have seen this asked several times: how to make a toggle button bar in Silverlight?
Some might call it toggle button group.

I have simply taken the ToggleButton template and dressed it on a RadioButton, with few minor changes like padding and corner radius.

Here is how it looks
Here is the source

Silverlight: DataGrid with integrated field (column) chooser

Not much to say here, it speaks for itself.

Right click on a column header to open the field (columns) chooser and show/hide columns.

The VisibleColumns property is always up-to-date and can be binded TwoWay, in my case I am saving it to the user preferences for consistency.

Take a look at the code to see how it’s done.

Notice: ContextMenu causes memory leak! http://silverlight.codeplex.com/workitem/7089. as long as you don’t need to remove the DataGrid you are OK.

Demo is Here
Source is here

the WPF version is over here

Silverlight: GridSplitter with a collapse button – BEST

I don’t remember where I took this control from.
It is not my work so I cannot take credit for it.
I have only fixed some stuff and made some changes to the animation.
This is by far the BEST version of this control! Use this one!
All credits goes to the anonymous developer who made it.
it should go into the Toolkit.

Demo is here
source is here