Phil Trelford's Array
POKE 36879, 255

Random Walker

May 30, 2013 00:10 by phil

This week our automated tools uncovered a fatal exception before it reached the client. Nick and Anton, 2 of the full-time testers on our team have written a testing tool they call Random Walker that simulates interactions with the user interface as clicks and gestures. It has helped uncover numerous issues, particularly NullReferenceExceptions, more often than not emanating from third-party libraries. It typically finds issues that are not easily found by manual testing or our extensive suite of unit and integration tests. Up-time is very important to us and our large client base who interact with our software throughout the day. 

Stack Trace

System.InvalidOperationException: GridViewColumnCollection is read-only now.
 at System.Windows.Controls.GridViewColumnCollection.VerifyAccess()
 at System.Windows.Controls.GridViewColumnCollection.RemoveItem(Int32 index)

Documentation

The exception is thrown after calling RemoveItem method of GridViewColumnCollection, although the MSDN documentation does not specify an exception is expected.

Disassembly

Using a disassembler we can see VerifyAccess will throw if the IsImmutable property is true:

private void VerifyAccess()
{
    if (this.IsImmutable)
    {
        throw new InvalidOperationException(
                      SR.Get("ListView_GridViewColumnCollectionIsReadOnly"));
    }
    base.CheckReentrancy();
}

 

The next question is when does this property get set to true. Using Telerik’s JustDecompile you can right click the property and select Find Usages, which shows its set from methods BlockWrite and UnblockWrite.

BlockWrite is called by the GridViewHeaderRowPresenter.StartHeaderDrag private method.

UnblockWrite is called by GridVIewHeaderRowPresenter.FinishHeaderDrag.

StartHeaderDrag is called from the MouseMove handler.

FinishHeadDrag is called from OnMouseLeftButtonUp, OnLostMouseCapture and OnColumnsPresenterKeyDown.

Problem

There appears to be an undocumented assumption that the columns can not be updated either during or on completion of a drag operation. However our front end lets you add or remove columns from a table via a separate menu, so it is possible for an update operation to come from outside the drag gesture.

Workaround

One workaround is to pre-empt the call to VerifyAccess by checking the IsImmutable property as it is possible to invoke private methods via refection:

bool IsImmutable()
{
    var flags = BindingFlags.Instance || BindingFlags.NonPublic;
    var property = columns.GetType().GetProperty("IsImmutable");
    return (bool)property.GetValue(columns, new object[] {});
}

 

Closing Thoughts

The Random Walker UI testing tool has really helped flush out those hard to find issues, if up-time is important to your customers, you may want to consider using one. Bugs exist even in mature frameworks like WPF, and you may need to set aside some time to working around them. Encapsulation seems good in theory, but I’m thankful that we can disassemble the source of third-party libraries and dynamically invoke private members when necessary.


Tags:
Categories: WPF | C# | .Net
Actions: E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Silverlight 5 Native Popup Windows

August 26, 2012 17:14 by phil

A popup is a window without a standard border that pops up above other controls to display contextual information like a tooltip, context menu or validation error. Silverlight’s browser heritage means it’s built-in popups may appear clipped inside their parent window.

Silverlight Validation Warning Clipped

When running on the desktop, as an out-of-browser full-trust application, it would be nice to have popups that appear outside of their parent window, as they do in WPF and WinForms,

Borderless

Silverlight 5’s native window support makes it possible to create popups that can break free of their parent window in out-of-browser applications. The main requirement for creating a native popup window is to be able to create a borderless window. A Silverlight Window can be made borderless simply by setting it’s WindowStyle property to None:

new Window
{
    Title = "Popup",
    WindowStyle = WindowStyle.None,
    TopMost = true,
    Visibility = Visibility.Collapsed
};

Note: set the Window’s width & height after you’ve set the style, otherwise it will appear with additional area to accommodate it’s no longer visible chrome.

Position

The absolute position of the popup can be set via the popup window’s Top and Left properties. To place the popup relative to a point in a parent window simply offset by the parent window’s Top and Left properties.

Example: Showing a Context Menu popup window on a right mouse button click:

private void MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
    var parent = Window.GetWindow((DependencyObject) sender);
    var position = e.GetPosition(null);
    var popup =
        new Window
        {
            WindowStyle = WindowStyle.None,
            TopMost = true,
            Top = parent.Top + position.Y,
            Left = parent.Left + position.X,
        };
}

Chrome

If the parent window has chrome then the absolute position should be offset by this too:

int chromeWidth = 0;
int chromeHeight = 0;
var style =
    window == Application.Current.MainWindow
    ? Deployment.Current.OutOfBrowserSettings.WindowSettings.WindowStyle
    : window.WindowStyle;
if (style == WindowStyle.SingleBorderWindow)
{
    chromeWidth = 10;
    chromeHeight = 32;
}

Note: getting the WindowStyle property of the main window throws a NotImplementedException exception. To workaround this the WindowStyle property of WindowSettings can be used instead if the parent window is the main window.

Target

WPF’s Popup control provides a PlacementTarget property which specifies:

the element relative to which the Popup is positioned when it opens

This can be useful for positioning tool tips or validation errors

Position

The TransformToVisual method can be used to get the position of an element in a window:

var window = Window.GetWindow(PlacementTarget);
var transform = PlacementTarget.TransformToVisual(window.Content);
var position = transform.Transform(new Point(0, 0));

The position of the popup can be offset using a HorizontalOffset and VerticalOffset.

Placement

The Placement property specifies the mode of the popup:

public enum PlacementMode
{
    Absolute,
    Relative,
    Top, 
    Left, 
    Bottom, 
    Right
}

Orientating the popup based on the placement mode:

private Point GetPlacementPosition()
{
    switch (Placement)
    {
        case PlacementMode.Absolute:
            return new Point(HorizontalOffset, VerticalOffset);
        case PlacementMode.Relative:
            return GetRelativePosition(0, 0);
        case PlacementMode.Top:
            return GetRelativePosition(0, -this.Height);
        case PlacementMode.Bottom:
            return GetRelativePosition(0, PlacementTarget.ActualHeight);
        case PlacementMode.Left:
            return GetRelativePosition(-this.Width, 0);
        case PlacementMode.Right:
            return GetRelativePosition(PlacementTarget.ActualWidth, 0);
        default:
            throw new InvalidOperationException();
    }
}

Note: the PlacementTarget’s ActualWidth and ActualHeight properties are useful here.

Monitors

Just as a Silverlight in-browser popup can be clipped inside a window, an out-of-browser window can be clipped inside the display monitor. Using P/Invoke it is easy to find monitor information from the MonitorFromPoint and GetMonitorInfo Win32 functions.

private void PlaceWindowWithinDisplay(Point point)
{
    var monitor = DisplayMonitors.GetMonitorInfoFromPoint(point);
    if (point.Y < monitor.WorkArea.Top)
        point.Y = monitor.WorkArea.Top;
    if (point.Y + this.Height > monitor.WorkArea.Bottom)
        point.Y = monitor.WorkArea.Bottom - this.Height;
    if (point.X < monitor.WorkArea.Left)
        point.X = monitor.WorkArea.Left;
    if (point.X + this.Width > monitor.WorkArea.Right)
        point.X = monitor.WorkArea.Right - this.Width;
    _window.Top = point.Y;
    _window.Left = point.X;
}

TaskBar

Popup windows should not typically be visible in the TaskBar. To remove a window from the task bar it’s extended style needs to be set to WS_EX_NOACTIVATE:

internal static void RemoveFromTaskBar(IntPtr hwnd)
{
    SetWindowLong(hwnd, GWL_EXSTYLE, (int)WS_EX_NOACTIVATE);
}

Again this can be achieved easily with P/Invoke, full details are available in this post:

Focus

WPF’s Popup provides a useful StaysOpen property which defaults to true and indicates:

whether the Popup control closes when the control is no longer in focus

Again this is easy to emulate by handling the GotFocus event on the popup’s parent window.

var window = Window.GetWindow(PlacementTarget);
window.Content.GotFocus += TargetWindowGotFocus;

Source

A full implementation of native Popup windows for Silverlight is available in the CodePlex Open Source project:

It also includes a Context Menu that works as a Popup:

ContextMenu_SL5_Native


Tags:
Categories: Silverlight | WPF | .Net | C#
Actions: E-mail | Permalink | Comments (1) | Comment RSSRSS comment feed

C# 5 CallerMemberName from Silverlight & WPF 4.0

August 25, 2012 09:13 by phil

C# 5 allows you to obtain the method or property of the caller to a method using the CallerMemberName attribute under System.Runtime.CompilerServices in .Net 4.5:

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ObservableObject : INotifyPropertyChanged 
{
  protected void NotifyPropertyChanged([CallerMemberName] string name = null)
  {
    var e = PropertyChanged;
    if (e != null) e(this, new PropertyChangedEventArgs(name));
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

This is particularly useful in XAML applications using WPF, Silverlight or WinRT that signal changes to properties via the INotifyPropertyChanged interface. With the new feature you don’t need to explicitly specify a literal string or lambda expression when notifying that a property has changed from it’s setter:

public class ViewModel : ObservableObject
{
  private object _value;

  public object Value
  {
    get { return _value; }
    set
    {
      _value = value;
      this.NotifyPropertyChanged();
    }
  }
}

Silverlight and earlier versions of the .Net Framework behind WPF do not have the CallerMemberName attribute. The good news is that you simply need to define it in yourself and assuming you’re using the C# 5 compiler then it just works:

namespace System.Runtime.CompilerServices
{
  /// <summary>
  /// Allows you to obtain the method or property name of the caller.
  /// </summary>
  [AttributeUsageAttribute(AttributeTargets.Parameter, Inherited = false)]
  public sealed class CallerMemberNameAttribute : Attribute { }
}

Tags:
Categories: C# | Silverlight | WPF | .Net
Actions: E-mail | Permalink | Comments (1) | Comment RSSRSS comment feed