Ramblings of a .NET Developer

17 December 2013

Tuesday, December 17, 2013 | by Paul | Categories: , , , | No comments
I recently had an interesting requirement. Basically, users were allowed access to any part of the application, but only users that had the correct permissions were allowed to change data. Further to this, the level of control required was down to the field level.

So, I couldn't just disable the window and be on my merry way. Also, as it was field level permissions I had to ignore validation for the field when it was read-only.

After having a think about this I decided to write a custom UserControl that would accept any content. This would have an IsEditable property and additional data source properties to use when the control was not editable.

So, some XAML...


    
        
    
    
    
        
        
        
                
    


As you can, there is nothing too special with the control. Basically it's just a layout grid that contains a ContentControl and a readonly TextBox. The visibility of these are toggled based on the IsEditable property and I used a TextBox to allow the user to still be able to copy data. Now the code behind...
    /// 
    /// Interaction logic for EditableControl.xaml
    /// 
    public partial class EditableControl : UserControl
    {
        public EditableControl()
        {
            InitializeComponent();
            DataContextChanged += OnDataContextChanged;
            GotFocus += EditableControl_GotFocus;
        }

        void EditableControl_GotFocus(object sender, RoutedEventArgs e)
        {
            try
            {
                var isShiftDown = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);

                if (!isShiftDown)
                {
                    if (Child != null && content.Visibility == System.Windows.Visibility.Visible && !Child.IsFocused && !Child.IsKeyboardFocusWithin)
                    {
                        Child.Focus();
                    }
                    else if (readonlyTextbox.Visibility == System.Windows.Visibility.Visible)
                    {
                        readonlyTextbox.Focus();
                    }
                }
            }
            catch
            {
                //exception could be thrown in some scenarios where child control shows a popup or context menu
            }
        }

        private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            UpdateSecurity(PropertyName);
        }

        public FrameworkElement Child
        {
            get { return (FrameworkElement)GetValue(ChildProperty); }
            set { SetValue(ChildProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Child.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ChildProperty =
            DependencyProperty.Register("Child", typeof(FrameworkElement), typeof(EditableControl), new UIPropertyMetadata(null));

        public string PropertyName
        {
            get { return (string)GetValue(PropertyNameProperty); }
            set { SetValue(PropertyNameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for PropertyName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.Register("PropertyName", typeof(string), typeof(EditableControl), new FrameworkPropertyMetadata(OnPropertyNameChanged));

        private static void OnPropertyNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = sender as EditableControl;
            var context = control.DataContext as ValidatingViewModel;
            if (e.NewValue == null)
                control.UpdateSecurity(string.Empty);
            else
                control.UpdateSecurity(e.NewValue.ToString());
        }

        private void UpdateSecurity(string name)
        {
            var context = DataContext as ViewModel;
            if (context == null)
            {
                Logger.Warn("DataContext not set");
            }
            else if (!string.IsNullOrEmpty(name))
            {
                IsEditable = context.CanEdit(name);
            }
        }

        public bool IsEditable
        {
            get { return (bool)GetValue(IsEditableProperty); }
            set { SetValue(IsEditableProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsEditable.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsEditableProperty =
            DependencyProperty.Register("IsEditable", typeof(bool), typeof(EditableControl), new UIPropertyMetadata(true, OnIsEditableChanged));

        private static void OnIsEditableChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = (RossEditableControl)sender;
            control.UpdateSecurity(control.PropertyName);
        }

        public string ReadonlySource
        {
            get { return (string)GetValue(ReadonlySourceProperty); }
            set { SetValue(ReadonlySourceProperty, value); }
        }

     public TextWrapping TextWrapping
     {
   get { return (TextWrapping)GetValue(TextWrappingProperty); }
   set { SetValue(TextWrappingProperty, value); }
     }

        // Using a DependencyProperty as the backing store for ReadonlySource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ReadonlySourceProperty =
            DependencyProperty.Register("ReadonlySource", typeof(string), typeof(EditableControl), new UIPropertyMetadata(null));

  public static readonly DependencyProperty TextWrappingProperty =
     DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(EditableControl), new UIPropertyMetadata(null));
    }
Here I've created DependencyProperties for Child, PropertyName, IsEditable, ReadonlySource and TextWrapping.
These allow enough configuration to allow us to customize how the readonly field is will be displayed. Also, I've not shown it here, but I have a style for the TextBox that will render the text in a gray italic font and gray border when it is read-only.
For the styling of the application, this was a good fit aesthetically.
I have a base ViewModel that exposes a virtual CanEdit function. This is used in the UpdateSecurity method to determine if the control should be readonly or not. This method is called when the PropertyName property changes.
For many scenarios this will work fine (i.e. the IsEditable set when the PropertyName gets set), but in some cases you will need it to be more dynamic. So, the IsEditable property could be bound to some property on your ViewModel.
Below is an example of how it would be used.

    
        
    


15 December 2013

Sunday, December 15, 2013 | by Paul | Categories: , , | No comments
I've worked with WPF since it was in beta, and although it's super flexible with what you can do, sometimes I just want to cut down on the repetitive xaml I have to write.

How many times have you bound the visibility of a control to some viewmodel property or even to the boolean property of another element?
Well, I've done this literally hundreds of times and realized the other day that I can make it easy with a simple Attached Property.
So, below is a drop-in helper class to make your binding easier without having to use a converter. Oh, use VisibilityHelper.Inverse="True" if you want the opposite result.

public class VisibilityHelper
    {

        public static bool GetIsVisible(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsVisibleProperty);
        }

        public static void SetIsVisible(DependencyObject obj, bool value)
        {
            obj.SetValue(IsVisibleProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsVisible.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsVisibleProperty =
            DependencyProperty.RegisterAttached("IsVisible", typeof(bool), typeof(VisibilityHelper), new UIPropertyMetadata(true, OnIsVisibleChanged));

        public static bool GetInverse(DependencyObject obj)
        {
            return (bool)obj.GetValue(InverseProperty);
        }

        public static void SetInverse(DependencyObject obj, bool value)
        {
            obj.SetValue(InverseProperty, value);
        }

        // Using a DependencyProperty as the backing store for Inverse.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InverseProperty =
            DependencyProperty.RegisterAttached("Inverse", typeof(bool), typeof(VisibilityHelper), new UIPropertyMetadata(false, OnIsVisibleChanged));

        private static void OnIsVisibleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            UpdateVisibility(sender as UIElement);
        }

        private static void UpdateVisibility(UIElement fe)
        {
            if (fe != null)
            {
                bool value = (bool)fe.GetValue(VisibilityHelper.IsVisibleProperty);
                if ((bool)fe.GetValue(VisibilityHelper.InverseProperty))
                {
                    fe.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
                }
                else
                {
                    fe.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
                }
            }

        }
    }

Now, add this to your favorite UI library and away you go!
As an example, the button below is hidden if it is disabled.

14 December 2013

Saturday, December 14, 2013 | by Paul | Categories: , , | No comments
So the projects I work on are usually quite large, often hitting over 1m lines of code. And, sometimes it is nice to add some buttons into the application which are there just for development purposes.

As one of my developers was hacking a way of doing this by leaving this out of his commits, it seemed like an opportunity to add something to reduce this management effort and eliminate errors when committing and pushing code in mecurial.

So, below is a simple attached property that will only render the control when the app is a DEBUG build.

    public static class Runtime
    {
        public static bool GetDebugOnly(DependencyObject obj)
        {
            return (bool)obj.GetValue(DebugOnlyProperty);
        }

        public static void SetDebugOnly(DependencyObject obj, bool value)
        {
            obj.SetValue(DebugOnlyProperty, value);
        }

        // Using a DependencyProperty as the backing store for DebugOnly.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DebugOnlyProperty =
            DependencyProperty.RegisterAttached("DebugOnly", typeof(bool), typeof(Runtime), new UIPropertyMetadata(false, OnDebugOnlyChanged));

        private static void OnDebugOnlyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var ui = sender as UIElement;
            if (ui != null)
            {
                if ((bool)e.NewValue)
                {
#if !DEBUG
                ui.Visibility = Visibility.Collapsed;
                ui.IsEnabled = false;
#endif
                }
            }
        }
    }

Now just just use the attached property where you need to.


Saturday, December 14, 2013 | by Paul | Categories: , | No comments
I had a requirement recently to provide the usual validation framework, but instead of showing error detail against every invalid control, the client wanted to show a small error bar when the control was not focused. When it receives focus, they then wanted the user to see the full validation error message.

The default validation does not cater for this scenario, but with the help of an attached property I was able to come up with something that works and is reusable.

First, the new class

    public class ValidationEx
    {
        public static bool GetAttach(DependencyObject obj)
        {
            return (bool)obj.GetValue(AttachProperty);
        }

        public static void SetAttach(DependencyObject obj, bool value)
        {
            obj.SetValue(AttachProperty, value);
        }

        // Using a DependencyProperty as the backing store for Attach.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AttachProperty =
            DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(ValidationEx), new FrameworkPropertyMetadata(false, AttachChanged));

        private static void AttachChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (sender is FrameworkElement)
            {
                var fe = sender as FrameworkElement;

                if (((bool)e.NewValue))
                {
                    fe.SetValue(Validation.ErrorTemplateProperty, Application.Current.Resources["LimitedValidationErrorTemplate"]);
                    fe.IsKeyboardFocusWithinChanged += KeyboardFocusWithinChanged;
                    fe.SizeChanged += ElementSizeChanged;
                    fe.LayoutUpdated += ElementLayoutUpdated;
                }
                else
                {
                    fe.SetValue(Validation.ErrorTemplateProperty, null);
                    fe.IsKeyboardFocusWithinChanged -= KeyboardFocusWithinChanged;
                    fe.SizeChanged -= ElementSizeChanged;
                    fe.LayoutUpdated -= ElementLayoutUpdated;
                }
            }
        }

        static void ElementLayoutUpdated(object sender, EventArgs e)
        {
            var fe = sender as FrameworkElement;

            if (fe != null && (bool)fe.GetValue(ValidationEx.AttachProperty))
            {
                UpdatePopupPosition(fe);
            }
        }

        public static string GetAttachWithTemplate(DependencyObject obj)
        {
            return (string)obj.GetValue(AttachWithTemplateProperty);
        }

        public static void SetAttachWithTemplate(DependencyObject obj, string value)
        {
            obj.SetValue(AttachWithTemplateProperty, value);
        }

        // Using a DependencyProperty as the backing store for AttachWithTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AttachWithTemplateProperty =
            DependencyProperty.RegisterAttached("AttachWithTemplate", typeof(string), typeof(ValidationEx), new FrameworkPropertyMetadata("", AttachWithTemplatechanged));

        private static void AttachWithTemplatechanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (sender is FrameworkElement)
            {
                var fe = sender as FrameworkElement;

                var template = Application.Current.Resources[e.NewValue.ToString()];
                fe.SetValue(Validation.ErrorTemplateProperty, template);

                fe.IsKeyboardFocusWithinChanged -= KeyboardFocusWithinChanged;
                fe.SizeChanged -= ElementSizeChanged;
                fe.LayoutUpdated -= ElementLayoutUpdated;

                fe.IsKeyboardFocusWithinChanged += KeyboardFocusWithinChanged;
                fe.SizeChanged += ElementSizeChanged;
                fe.LayoutUpdated += ElementLayoutUpdated;
            }
        }

        static void ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var fe = Keyboard.FocusedElement as FrameworkElement;

            if (fe != null && (bool)fe.GetValue(ValidationEx.AttachProperty))
            {
                UpdatePopupPosition(fe);
            }
        }

        private static ScrollViewer GetScrollViewer(FrameworkElement fe)
        {
            var parent = VisualTreeHelper.GetParent(fe);
            while (parent != null && !(parent is Window))
            {
                if (parent is ScrollViewer)
                    return parent as ScrollViewer;

                if (parent is Border)
                {
                    var child = ((Border)parent).Child;
                    if (child is ScrollViewer)
                        return child as ScrollViewer;
                }

                parent = VisualTreeHelper.GetParent(parent);
            }

            return null;
        }

        static void ElementSizeChanged(object sender, SizeChangedEventArgs e)
        {
            FrameworkElement fe = sender as FrameworkElement;
            if (fe != null && fe.IsKeyboardFocusWithin)
            {
                UpdatePopupPosition(fe);
            }
        }

        private static void UpdatePopupPosition(FrameworkElement fe)
        {
            KeyboardFocusWithinChanged(fe, new DependencyPropertyChangedEventArgs(FrameworkElement.IsKeyboardFocusWithinProperty, true, false));
            KeyboardFocusWithinChanged(fe, new DependencyPropertyChangedEventArgs(FrameworkElement.IsKeyboardFocusWithinProperty, false, true));
        }

        private static void KeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement fe = sender as FrameworkElement;
            if (fe != null)
            {
                var scrollViewer = GetScrollViewer(fe);

                if (((bool)e.NewValue))
                {
                    var value = fe.GetValue(Validation.ErrorTemplateProperty);
                    var resourceKey = fe.GetValue(ValidationEx.AttachWithTemplateProperty).ToString();
                    if (string.IsNullOrWhiteSpace(resourceKey))
                        resourceKey = "ValidationErrorTemplate";

                    var template = Application.Current.Resources[resourceKey];
                    fe.SetValue(Validation.ErrorTemplateProperty, template);

                    if (scrollViewer != null)
                    {
                        scrollViewer.ScrollChanged -= ScrollChanged;
                        scrollViewer.ScrollChanged += ScrollChanged;
                    }
                }
                else
                {
                    fe.SetValue(Validation.ErrorTemplateProperty, Application.Current.Resources["LimitedValidationErrorTemplate"]);

                    if (scrollViewer != null)
                    {
                        scrollViewer.ScrollChanged -= ScrollChanged;
                    }
                }
            }
        }
    }

This makes available two attached properties.

Attach – which triggers the code to use custom templates
AttachWithTemplate – which allows you to specify a custom validation template key.

So, the above code specifies the validation template to use when the control does not have focus. When it or anyone of its children gets focus, the validation template is changed to reflect the different UI requirement.

The Resource Dictionary

Of course, nothing will look any different if we don’t code up the templates. So, below are some basic templates to use.

    

    
    
        
            
            
        
    

    
        
            
            
                
                    
                        
                            
                                
                            
                        
                    
                
            
        
     


To use the validation extension I have a different resource dictionary where I've specified various settings. So, for a Textbox I've set a global style to hook up the validation extension.


So to visualize this, here are a few screenshots to help.

Here, the last name field is not focused, so the last name validation shows a bar at the side.

Hovering over bar shows the details of the error

Once focused, the bar is replaced with a custom validation message which will display the current error.