Notes on Software

Shared ResourceDictionary for Silverlight

Posted by K. M. on April 5, 2011

The problem:

Both WPF and Silverlight support merged ResourceDictionary usage via the MergedDictionaries property.  Typically this is done with markup similar to the following in App.xaml

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Shared.xaml"/>
                <ResourceDictionary Source="Resources/Views1.xaml"/>
                <ResourceDictionary Source="Resources/Views2.xaml"/>
                <ResourceDictionary Source="Resources/Views3.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>

In WPF any resources defined within Shared.xaml are accessible by a StaticResource reference from any of the other dictionaries because of the order in which they are merged into App.xaml. In Silverlight, all the dictionaries are loaded independently and StaticResource references across dictionaries do not work. To get the references to work, Shared.xaml has to be merged into each of the other dictionaries. However, this means that the contents of Shared.xaml will be loaded and duplicated at each place where it is merged in. This is clearly bad. While searching for a solution I came across this post by Christian Mosers. Mosers defines a SharedResourceDictionary class deriving from ResourceDictionary and redefines the Source property where he uses some caching logic to avoid reloading the dictionary. Unfortunately this solution does not work in Silverlight. For some reason, Silverlight does not allow the same dictionary instance to be merged into multiple dictionaries. Additionally, the Visual Studio designer throws an exception in the setter of the Source property. Apparantly Visual Studio does not like derived classes.

The solution:

The following class solves both the problems detailed above

public class SharedResourceDictionary : ResourceDictionary
{
    public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
       new Dictionary<Uri, ResourceDictionary>();

    private Uri _sourceUri;
    public new Uri Source
    {
        get { return _sourceUri; }
        set
        {
            _sourceUri = value;
            if (!_sharedDictionaries.ContainsKey(value))
            {
#if SILVERLIGHT
                var dict = new ResourceDictionary();
                Application.LoadComponent(dict, value);
#else
                var dict = (ResourceDictionary)Application.LoadComponent(value);
#endif
                _sharedDictionaries.Add(value, dict);
            }
            CopyInto(this, _sharedDictionaries[value]);
        }
    }

    private static void CopyInto(ResourceDictionary copy, ResourceDictionary original)
    {
        foreach (var dictionary in original.MergedDictionaries)
        {
            var mergedCopy = new ResourceDictionary();
            CopyInto(mergedCopy, dictionary);
            copy.MergedDictionaries.Add(mergedCopy);
        }
        foreach (DictionaryEntry pair in original)
        {
            copy.Add(pair.Key, pair.Value);
        }
    }
}

    This bypasses the ResourceDictionary.Source property entirely by using Application.LoadComponent directly, thus keeping the Visual Studio designer happy. It copies the cached ResourceDictionary to keep Silverlight happy. This is slightly less efficient than merging in the same cached instance everywhere but it is certainly much better than reloading every resource multiple times. The only catch is that the Source property must be set in a way that Application.LoadComponent can resolve it correctly. I use this with absolute URIs.

Sample usage:

Markup similar to the following can be used at the beginning of each resource dictionary that needs to reference resources in Shared.xaml

    <ResourceDictionary.MergedDictionaries>
        <ui:SharedResourceDictionary Source="/my_assembly_name;component/Resources/Shared.xaml"/>
    </ResourceDictionary.MergedDictionaries>

Posted in Silverlight, WPF | Tagged: , , | 15 Comments »

Differences between WPF and Silverlight – 1

Posted by K. M. on February 25, 2011

This is the first post in a series documenting the differences between WPF (v3.5+) and Silverlight (v4).

DataTemplates in an ItemsControl in bound mode (when ItemsSource is set):

An ItemsControl generates a container for each item in the ItemsSource. The container typically displays the item via a ContentPresenter (either directly or through a ControlTemplate for the container).

WPF:

The logic in the PrepareContainerForItemOverride method sets the ContentTemplate of the container to the ItemTemplate property if it is not null and the ContentTemplateSelector to the ItemTemplateSelector if it is not null. Additionally, the ItemContainerStyle gets applied to the container. This results in the following order of precedence for the data template

  1. ItemsControl.ItemTemplate
  2. ContentTemplate from ItemsControl.ItemContainerStyle
  3. ItemsControl.ItemTemplateSelector
  4. ContentTemplateSelector from ItemsControl.ItemContainerStyle

Silverlight:

There is no DataTemplateSelector. Also the logic in the PrepareContainerForItemOverride method sets the ContentTemplate of the container to the ItemTemplate property even if it is null. This results in the ItemsControl always using the ItemTemplate property for the data template. The ContentTemplate from ItemsControl.ItemContainerStyle is always ignored.

My comments:

The silverlight implementation is broken and fixing it will mean a breaking change. So I doubt if it will ever be fixed. It seems that the developers who implemented this either did not understand the concept of dependency property precedence or did not care to compare the implementation with the WPF one. Bad.

Posted in .NET, Silverlight, WPF | Tagged: , , , | Leave a Comment »

Productivity tip for Windows Explorer

Posted by K. M. on January 2, 2011

To set the focus on the list view in the windows explorer, press Ctrl+E (takes you to the search box) and then the down key (takes you down to the list view). No more pressing tab until the focus is at the desired place.

Note: This will not work on XP since there is no search box on XP.

Posted in Uncategorized | Leave a Comment »

HeaderedScrollViewer in WPF

Posted by K. M. on November 7, 2009

I needed a scrollviewer with a header area on the top that does not scroll vertically but does scroll horizontally. It was pretty simple to derive from ScrollViewer, define a TopHeader property (and a LeftHeader property for good measure), modify the control template to contain ScrollViewers for the headers with hidden scrollbars and synchronize the horizontal and vertical offsets in code. This worked well until I put a ComboBox in the header. Suddenly the scroll position started getting reset to zero when the ComboBox was opened. I looked on the internet for a solution but didn’t find one. A little debugging revealed that when the ComboBox is opened, the ScrollViewer in the popup raises a ScrollChanged event which routes up to the ScrollViewer containing the header. To fix this problem all I needed to do was to ignore events not originating directly from the header ScrollViewers.

Here is the code

    [TemplatePart(Name = "PART_TopHeaderScrollViewer", Type = typeof(ScrollViewer))]
    [
TemplatePart(Name = "PART_LeftHeaderScrollViewer", Type = typeof(ScrollViewer))]
    
public class HeaderedScrollViewer : ScrollViewer
    {
        
static HeaderedScrollViewer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
typeof(HeaderedScrollViewer), new FrameworkPropertyMetadata(typeof(HeaderedScrollViewer)));
        }

        public object TopHeader
        {
            
get { return (object)GetValue(TopHeaderProperty); }
            
set { SetValue(TopHeaderProperty, value); }
        }
        
public static readonly DependencyProperty TopHeaderProperty =
            
DependencyProperty.Register("TopHeader", typeof(object), typeof(HeaderedScrollViewer), new UIPropertyMetadata(TopHeader_PropertyChanged));
        
private static void TopHeader_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            
HeaderedScrollViewer instance = (HeaderedScrollViewer)d;
            
if (e.OldValue != null)
                instance.RemoveLogicalChild(e.OldValue);
            
if (e.NewValue != null)
                instance.AddLogicalChild(e.NewValue);
        }

        public DataTemplate TopHeaderTemplate
        {
            
get { return (DataTemplate)GetValue(TopHeaderTemplateProperty); }
            
set { SetValue(TopHeaderTemplateProperty, value); }
        }
        
public static readonly DependencyProperty TopHeaderTemplateProperty =
            
DependencyProperty.Register("TopHeaderTemplate", typeof(DataTemplate), typeof(HeaderedScrollViewer), new UIPropertyMetadata());

        public object LeftHeader
        {
            
get { return (object)GetValue(LeftHeaderProperty); }
            
set { SetValue(LeftHeaderProperty, value); }
        }
        
public static readonly DependencyProperty LeftHeaderProperty =
            
DependencyProperty.Register("LeftHeader", typeof(object), typeof(HeaderedScrollViewer), new UIPropertyMetadata(LeftHeader_PropertyChanged));
        
private static void LeftHeader_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            
HeaderedScrollViewer instance = (HeaderedScrollViewer)d;
            
if (e.OldValue != null)
                instance.RemoveLogicalChild(e.OldValue);
            
if (e.NewValue != null)
                instance.AddLogicalChild(e.NewValue);
        }

        public DataTemplate LeftHeaderTemplate
        {
            
get { return (DataTemplate)GetValue(LeftHeaderTemplateProperty); }
            
set { SetValue(LeftHeaderTemplateProperty, value); }
        }
        
public static readonly DependencyProperty LeftHeaderTemplateProperty =
            
DependencyProperty.Register("LeftHeaderTemplate", typeof(DataTemplate), typeof(HeaderedScrollViewer), new UIPropertyMetadata());

        protected override System.Collections.IEnumerator LogicalChildren
        {
            
get
            {
                
if (TopHeader != null || LeftHeader != null)
                {

                    System.Collections.ArrayList children = new System.Collections.ArrayList();
                    System.Collections.
IEnumerator baseEnumerator = base.LogicalChildren;
                    
while (baseEnumerator.MoveNext())
                    {
                        children.Add(baseEnumerator.Current);
                    }
                    
if (TopHeader != null)
                        children.Add(TopHeader);
                    
if (LeftHeader != null)
                        children.Add(LeftHeader);
                    
return children.GetEnumerator();
                }
                
return base.LogicalChildren;
            }
        }

        private ScrollViewer topHeaderScrollViewer;

        private ScrollViewer leftHeaderScrollViewer;

        public override void OnApplyTemplate()
        {
            
base.OnApplyTemplate();
            topHeaderScrollViewer =
base.GetTemplateChild("PART_TopHeaderScrollViewer") as ScrollViewer;
            
if (topHeaderScrollViewer != null)
                topHeaderScrollViewer.ScrollChanged += topHeaderScrollViewer_ScrollChanged;
            leftHeaderScrollViewer =
base.GetTemplateChild("PART_LeftHeaderScrollViewer") as ScrollViewer;
            
if (leftHeaderScrollViewer != null)
                leftHeaderScrollViewer.ScrollChanged += leftHeaderScrollViewer_ScrollChanged;
        }

        private void leftHeaderScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            
if (e.OriginalSource == leftHeaderScrollViewer)
                ScrollToVerticalOffset(e.VerticalOffset);
        }

        private void topHeaderScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            
if (e.OriginalSource == topHeaderScrollViewer)
                ScrollToHorizontalOffset(e.HorizontalOffset);
        }

        protected override void OnScrollChanged(ScrollChangedEventArgs e)
        {
            
base.OnScrollChanged(e);
            
if (topHeaderScrollViewer != null)
                topHeaderScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
            
if (leftHeaderScrollViewer != null)
                leftHeaderScrollViewer.ScrollToVerticalOffset(e.VerticalOffset);
        }
    }

<Style TargetType="{x:Type local:HeaderedScrollViewer}">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Focusable" Value="False"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:HeaderedScrollViewer}">
                <Grid Margin="{TemplateBinding Padding}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <ScrollViewer Name="PART_TopHeaderScrollViewer" Grid.Column="1"
                                     HorizontalScrollBarVisibility="Hidden"
                                     VerticalScrollBarVisibility="Hidden"
                                     Focusable="false">
                        <ContentPresenter Content="{TemplateBinding TopHeader}" MinWidth="{TemplateBinding ExtentWidth}"
                                         ContentTemplate="{TemplateBinding TopHeaderTemplate}"/>
                    </ScrollViewer>
                    <ScrollViewer Name="PART_LeftHeaderScrollViewer" Grid.Row="1"
                                     HorizontalScrollBarVisibility="Hidden"
                                     VerticalScrollBarVisibility="Hidden"
                                     Focusable="false">
                        <ContentPresenter Content="{TemplateBinding LeftHeader}" MinHeight="{TemplateBinding ExtentHeight}"
                                         ContentTemplate="{TemplateBinding LeftHeaderTemplate}"/>
                    </ScrollViewer>
                    <AdornerDecorator x:Name="test" Grid.Row="1" Grid.Column="1">
                        <ScrollContentPresenter Name="PART_ScrollContentPresenter"
                                               KeyboardNavigation.DirectionalNavigation="Local"
                                               Content="{TemplateBinding Content}"
                                               ContentTemplate="{TemplateBinding ContentTemplate}"
                                               CanContentScroll="{TemplateBinding CanContentScroll}"
                                               SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </AdornerDecorator>
                    <ScrollBar Name="PART_HorizontalScrollBar"
                               Orientation="Horizontal"
                               Grid.Row="2" Grid.ColumnSpan="2"
                               Minimum="0.0"
                               Maximum="{TemplateBinding ScrollableWidth}"
                               ViewportSize="{TemplateBinding ViewportWidth}"
                               Value="{Binding Path=HorizontalOffset,RelativeSource={RelativeSource TemplatedParent},Mode=OneWay}"
                               Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                               Cursor="Arrow"/>
                    <ScrollBar Name="PART_VerticalScrollBar"
                               Orientation="Vertical"
                               Grid.Column="2" Grid.RowSpan="2"
                               Minimum="0.0"
                               Maximum="{TemplateBinding ScrollableHeight}"
                               ViewportSize="{TemplateBinding ViewportHeight}"
                               Value="{Binding Path=VerticalOffset,RelativeSource={RelativeSource TemplatedParent},Mode=OneWay}"
                               Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                               Cursor="Arrow"/>
                    <DockPanel Grid.Column="2"
                               Grid.Row="2"
                               Background="{Binding Path=Background,ElementName=PART_VerticalScrollBar}"
                               LastChildFill="false">
                        <Rectangle DockPanel.Dock="Left"
                                   Width="1"
                                   Fill="White"
                                   Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
                        <Rectangle DockPanel.Dock="Top"
                                   Height="1"
                                   Fill="White"
                                   Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
                    </DockPanel>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Posted in WPF | Tagged: , , , | 5 Comments »

Improve XAML editing experience in Visual Studio

Posted by K. M. on February 21, 2009

I have been quite busy for some time to put up any posts but this XAML tip (thanks to Fabrice) just needs to be “out there”

  1. Right-click on a XAML file in the Solution Explorer
  2. Select “Open With…”
  3. Select “Source Code (Text) Editor”
  4. Click on “Set as Default”
  5. Click OK
  6. You’re done!

This completely eliminates the useless XAML designer while still providing intellisense and tag collapsing.

Posted in Uncategorized, WPF | Tagged: , , | Comments Off

Using BindingGroup in custom controls

Posted by K. M. on September 25, 2008

I have a WPF custom control to display and edit a double value. The control exposes a dependency property called Value, along with a Format, Converter and ConverterParameter properties. The control template has a TextBox whose Text property is bound to the Value property with a custom converter.

private void SetValueBinding()
{
    
if (textbox == null) return;
    
textbox.SetBinding(TextBox.TextProperty, new Binding(“Value”)
    {
        Source =
this,
        Converter =
new CustomConverter() { Converter = Converter, Format = Format },
        ConverterParameter = ConverterParameter,
        Mode =
BindingMode.TwoWay,
        ValidatesOnExceptions =
true
    });
}

(Here textbox is the instance of the TextBox that must be a part of the ControlTemplate, and the SetValueBinding method is called in the OnApplyTemplate override after obtaining the textbox instance)
This works well except for validation. If the user types in some text in the custom control that cannot be converted to a double, the Binding shown above fails. But this binding error does not participate in the validation mechanism (look at my earlier post on validation for a pattern for implementing validation). The solution is to find a BindingGroup on a parent of the custom control and add the BindingExpression to the BindingGroup.

private BindingGroup GetParentBindingGroup()
{
    
FrameworkElement element = this;
    
while (element != null)
    {
        
BindingGroup grp = element.BindingGroup;
        
if (grp != null)
            
return grp;
        element =
VisualTreeHelper.GetParent(element) as FrameworkElement;
    }
    
return null;
}

This can now be used in the SetValueBinding method

private void SetValueBinding()
{
    
if (textbox == null) return;
    
BindingGroup group = GetParentBindingGroup();
    
BindingExpressionBase expr =
        
BindingOperations.GetBindingExpressionBase(textbox, TextBox.TextProperty);
    
if (group != null && expr != null)
        group.BindingExpressions.Remove(expr);
    textbox.SetBinding(
TextBox.TextProperty, new Binding(“Value”)
    {
        Source =
this,
        Converter =
new CustomConverter() { Converter = Converter, Format = Format },
        ConverterParameter = ConverterParameter,
        Mode =
BindingMode.TwoWay,
        ValidatesOnExceptions =
true
    });
    expr =
BindingOperations.GetBindingExpressionBase(textbox, TextBox.TextProperty);
    
if (group != null)
        group.BindingExpressions.Add(expr);
}

Posted in WPF | Tagged: , | Leave a Comment »

JPEG Images in WPF

Posted by K. M. on September 1, 2008

I recently implemented a very simple help system for a WPF application consisting of showing an image in a help window with the selection of the image based on the application state. The images were all jpegs. The system worked well on Vista – my development machine – but on an XP test machine, loading the first image took inordinately long - around a minute. The subsequent images loaded instantaneously. For the moment, I have solved the issue by converting all images to PNG. It looks like some problem with loading the JPEG decoder. Need to investigate this further.

Posted in WPF | Tagged: , | Leave a Comment »

Validation in WPF with .NET 3.5 SP1

Posted by K. M. on August 20, 2008

With the new BindingGroup support in WPF, validation in WPF can be handled much more cleanly than it was possible in the past. Here is a pattern that I believe I will be using fairly often.

In the data layer:

Make all domain objects implement IEditableObject and IDataErrorInfo and INotifyPropertyChanged.

Apply property level validation logic in the property setters of the domain objects. Throw exceptions when the logic is violated. (Example: Throw an exception if a name is null or empty or too long, Throw an exception if a number is negative etc.)

Apply object level validation logic in the implementation of IDataErrorInfo.Error. Do not throw exceptions when object level validation logic is violated.

Return null in the implementation of IDataErrorInfo.this[string ColumnName].

In the WPF layer:

Use a BindingGroup on the container that contains the editable UI elements

Use an instance of the following ValidationRule on the BindingGroup

public class DataErrorValidationRule : ValidationRule
{
    
public override ValidationResult Validate(object value,
        System.Globalization.
CultureInfo cultureInfo)
    {
        
BindingGroup group = (BindingGroup)value;
        
// if any expression is in error, the object level validation
        // cannot be trusted, since atleast some properties have not been
        // set. Better to skip object level validation than report
        // spurious errors
        if (group.BindingExpressions.Any(be => be.HasError))
            
return ValidationResult.ValidResult;
        
StringBuilder sb = null;
        
foreach (var item in group.Items)
        {
// Unlikely to have more than one item, but loop through anyway
            IDataErrorInfo info = item as IDataErrorInfo;
            
if (info != null)
            {
                
string error = info.Error;
                
if (!string.IsNullOrEmpty(error))
                {
                    
if (sb == null) sb = new StringBuilder();
                    
if (sb.Length != 0) sb.AppendLine();
                    sb.Append(error);
                }
            }
        }
        
if (sb != null)
            
return new ValidationResult(false, sb.ToString());
        
return ValidationResult.ValidResult;
    }
}

Explicitly set UpdateSourceTrigger to PropertyChanged or LostFocus on all bindings in the BindingGroup.

Set ValidatesOnExceptions to True on all bindings in the BindingGroup.

Define Edit, Accept and Cancel RoutedUICommands. Call BeginEdit, CommitEdit and CancelEdit on the BindingGroup when the commands are executed. Call ValidateWithoutUpdate and set CanExecuteRoutedEventArgs.CanExecute to the Validation.HasError property on the container element in the CanExecute event handler for the Accept command.

Use the following code to extract the error message from a ValidationError for display in the UI.

internal static string ExtractErrorMessage(ValidationError error)
{
    
if (error.Exception is TargetInvocationException &&
        error.Exception.Message == (
string)error.ErrorContent)
        
return error.Exception.InnerException.Message;
    
else
        return System.Convert.ToString(error.ErrorContent);
}

The code for all the work in the WPF layer can be abstracted into a static class with attached properties for use in XAML. This class provides the following features

  • IsEditableContainer attached property (Set to True on the container containing all the editing UI) This can be set both on a stand alone container or a container in an ItemsControl such as a ListBoxItem via a Style. When used in an ItemsControl, the code calls EditItemCommitEdit and CancelEdit on the IEditableCollectionView used by the ItemsControl.
  • IsEditingEnabled attached property (Set to False to disable editing. Can be bound to ListBoxItem.IsSelected for example)
  • IsDeletingEnabled attached property (Set to True to allow deleting the data item. Only relevant if the data item is in a collection and is being displayed in an ItemsControl)
  • EditGesture, AcceptGesture, CancelGesture and DeleteGesture attached properties. Specify KeyGestures for the Edit, Accept, Cancel and Delete commands
  • Deleting routed event which bubbles up the element tree. Use to cancel (possibly with a confirm dialog box) the execution of the Delete command
public static class DataEditing
{
    
public static bool GetIsEditableContainer(DependencyObject obj)
    {
        
return (bool)obj.GetValue(IsEditableContainerProperty);
    }
    
public static void SetIsEditableContainer(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEditableContainerProperty, value);
    }
    
public static readonly DependencyProperty IsEditableContainerProperty =
        
DependencyProperty.RegisterAttached(“IsEditableContainer”, typeof(bool), typeof(DataEditing),
            
new FrameworkPropertyMetadata(IsEditableContainer_PropertyChanged));
    
private static void IsEditableContainer_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        
FrameworkElement element = d as FrameworkElement;
        
if (element == null) return;
        
bool editable = (bool)e.NewValue;
        
if (editable)
        {
            
BindingGroup group = new BindingGroup();
            group.ValidationRules.Add(
new DataErrorValidationRule());
            element.BindingGroup = group;
            element.CommandBindings.Add(
new CommandBinding(Edit, OnExecuteEditCommand, CanExecuteEditCommand));
            element.CommandBindings.Add(
new CommandBinding(Accept, OnExecuteAcceptCommand, CanExecuteAcceptCommand));
            element.CommandBindings.Add(
new CommandBinding(Cancel, OnExecuteCancelCommand, CanExecuteCancelCommand));
            element.CommandBindings.Add(
new CommandBinding(Delete, OnExecuteDeleteCommand, CanExecuteDeleteCommand));
        }
        
else
        {
            element.BindingGroup =
null;
            element.CommandBindings.Remove((
CommandBinding b) => b.Command == Edit);
            element.CommandBindings.Remove((
CommandBinding b) => b.Command == Accept);
            element.CommandBindings.Remove((
CommandBinding b) => b.Command == Cancel);
            element.CommandBindings.Remove((
CommandBinding b) => b.Command == Delete);
        }
    }

    public static bool GetIsEditingEnabled(DependencyObject obj)
    {
        
return (bool)obj.GetValue(IsEditingEnabledProperty);
    }
    
public static void SetIsEditingEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEditingEnabledProperty, value);
    }
    
public static readonly DependencyProperty IsEditingEnabledProperty =
        
DependencyProperty.RegisterAttached(“IsEditingEnabled”, typeof(bool), typeof(DataEditing),
            
new FrameworkPropertyMetadata(true));

    public static bool GetIsDeletingEnabled(DependencyObject obj)
    {
        
return (bool)obj.GetValue(IsDeletingEnabledProperty);
    }
    
public static void SetIsDeletingEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsDeletingEnabledProperty, value);
    }
    
public static readonly DependencyProperty IsDeletingEnabledProperty =
        
DependencyProperty.RegisterAttached(“IsDeletingEnabled”, typeof(bool), typeof(DataEditing),
            
new FrameworkPropertyMetadata(false));

    public static KeyGesture GetEditGesture(DependencyObject obj)
    {
        
return (KeyGesture)obj.GetValue(EditGestureProperty);
    }
    
public static void SetEditGesture(DependencyObject obj, KeyGesture value)
    {
        obj.SetValue(EditGestureProperty, value);
    }
    
public static readonly DependencyProperty EditGestureProperty =
        
DependencyProperty.RegisterAttached(“EditGesture”, typeof(KeyGesture), typeof(DataEditing),
            
new FrameworkPropertyMetadata(CommandGesture_PropertyChanged));

    public static KeyGesture GetAcceptGesture(DependencyObject obj)
    {
        
return (KeyGesture)obj.GetValue(AcceptGestureProperty);
    }
    
public static void SetAcceptGesture(DependencyObject obj, KeyGesture value)
    {
        obj.SetValue(AcceptGestureProperty, value);
    }
    
public static readonly DependencyProperty AcceptGestureProperty =
        
DependencyProperty.RegisterAttached(“AcceptGesture”, typeof(KeyGesture), typeof(DataEditing),
            
new FrameworkPropertyMetadata(CommandGesture_PropertyChanged));

    public static KeyGesture GetCancelGesture(DependencyObject obj)
    {
        
return (KeyGesture)obj.GetValue(CancelGestureProperty);
    }
    
public static void SetCancelGesture(DependencyObject obj, KeyGesture value)
    {
        obj.SetValue(CancelGestureProperty, value);
    }
    
public static readonly DependencyProperty CancelGestureProperty =
        
DependencyProperty.RegisterAttached(“CancelGesture”, typeof(KeyGesture), typeof(DataEditing),
            
new FrameworkPropertyMetadata(CommandGesture_PropertyChanged));

    public static KeyGesture GetDeleteGesture(DependencyObject obj)
    {
        
return (KeyGesture)obj.GetValue(DeleteGestureProperty);
    }
    
public static void SetDeleteGesture(DependencyObject obj, KeyGesture value)
    {
        obj.SetValue(DeleteGestureProperty, value);
    }
    
public static readonly DependencyProperty DeleteGestureProperty =
        
DependencyProperty.RegisterAttached(“DeleteGesture”, typeof(KeyGesture), typeof(DataEditing),
            
new FrameworkPropertyMetadata(CommandGesture_PropertyChanged));

    private static void CommandGesture_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        
DependencyProperty dp = e.Property;
        
RoutedUICommand command =
            dp == EditGestureProperty ? Edit :
            dp == AcceptGestureProperty ? Accept :
            dp == CancelGestureProperty ? Cancel :
            dp == DeleteGestureProperty ? Delete :
            
null;
        
if (command != null)
        {
            
KeyGesture gesture = (KeyGesture)e.OldValue;
            
FrameworkElement element = (FrameworkElement)d;
            
if (gesture != null)
                element.InputBindings.Remove((
InputBinding ib) => ib.Command == command && ib.Gesture == gesture);
            gesture = (
KeyGesture)e.NewValue;
            
if (gesture != null)
                element.InputBindings.Add(
new InputBinding(command, gesture));
        }
    }

    private static bool GetViewAndItem(FrameworkElement element, out IEditableCollectionView view, out object dataItem)
    {
        
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(element);
        
if (ic != null)
        {
            
object item = ic.ItemContainerGenerator.ItemFromContainer(element);
            
if (item != null && item != DependencyProperty.UnsetValue)
            {
                view = (
IEditableCollectionView)ic.Items;
                dataItem = item;
                
return true;
            }
        }
        view =
null;
        dataItem =
null;
        
return false;
    }

    private static void CanExecuteEditCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;
        e.CanExecute = GetIsEditingEnabled(element) && GetIsReadOnly(element);
    }
    
private static void OnExecuteEditCommand(object sender, ExecutedRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;

        IEditableCollectionView view;
        
object item;
        
if (GetViewAndItem(element, out view, out item))
            view.EditItem(item);
        element.BindingGroup.BeginEdit();

        SetIsReadOnly(element, false);
    }

    private static void CanExecuteAcceptCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;
        element.BindingGroup.ValidateWithoutUpdate();
        e.CanExecute = !GetIsReadOnly(element) && !
Validation.GetHasError(element);
    }
    
private static void OnExecuteAcceptCommand(object sender, ExecutedRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;
        element.BindingGroup.CommitEdit();

        IEditableCollectionView view;
        
object item;
        
if (GetViewAndItem(element, out view, out item))
            view.CommitEdit();

        SetIsReadOnly(element, true);
    }

    private static void CanExecuteCancelCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;
        e.CanExecute = !GetIsReadOnly(element);
    }
    
private static void OnExecuteCancelCommand(object sender, ExecutedRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;
        element.BindingGroup.CancelEdit();

        IEditableCollectionView view;
        
object item;
        
if (GetViewAndItem(element, out view, out item))
            view.CancelEdit();

        SetIsReadOnly(element, true);
    }

    private static void CanExecuteDeleteCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;
        e.CanExecute = GetIsDeletingEnabled(element);
    }
    
private static void OnExecuteDeleteCommand(object sender, ExecutedRoutedEventArgs e)
    {
        
FrameworkElement element = sender as FrameworkElement;

        IEditableCollectionView view;
        
object item;
        
if (GetViewAndItem(element, out view, out item))
        {
            
CancelRoutedEventArgs cancelArgs = new CancelRoutedEventArgs() { RoutedEvent = DeletingEvent, Source = element };
            element.RaiseEvent(cancelArgs);
            
if (!cancelArgs.Cancel)
                view.Remove(item);
        }
    }

    public static bool GetIsReadOnly(DependencyObject obj)
    {
        
return (bool)obj.GetValue(IsReadOnlyProperty);
    }
    
private static void SetIsReadOnly(DependencyObject obj, bool value)
    {
        obj.SetValue(IsReadOnlyPropertyKey, value);
    }
    
public static readonly DependencyPropertyKey IsReadOnlyPropertyKey =
        
DependencyProperty.RegisterAttachedReadOnly(“IsReadOnly”, typeof(bool), typeof(DataEditing),
        
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));
    
public static readonly DependencyProperty IsReadOnlyProperty = IsReadOnlyPropertyKey.DependencyProperty;

    public static readonly RoutedUICommand Edit = new RoutedUICommand(“Edit”, “Edit”, typeof(DataEditing));
    
public static readonly RoutedUICommand Accept = new RoutedUICommand(“Accept”, “Accept”, typeof(DataEditing));
    
public static readonly RoutedUICommand Cancel = new RoutedUICommand(“Cancel”, “Cancel”, typeof(DataEditing));
    
public static readonly RoutedUICommand Delete = new RoutedUICommand(“Delete”, “Delete”, typeof(DataEditing));

    public static void AddDeletingHandler(DependencyObject element, CancelRoutedEventHandler handler)
    {
        ((
FrameworkElement)element).AddHandler(DeletingEvent, handler);
    }
    
public static void RemoveDeletingHandler(DependencyObject element, CancelRoutedEventHandler handler)
    {
        ((
FrameworkElement)element).RemoveHandler(DeletingEvent, handler);
    }
    
public static readonly RoutedEvent DeletingEvent =
        
EventManager.RegisterRoutedEvent(“Deleting”, RoutingStrategy.Bubble,
            
typeof(CancelRoutedEventHandler), typeof(DataEditing));

    private static void Remove<T>(this System.Collections.IList list, Func<T, bool> predicate)
    {
        T item = list.OfType<T>().FirstOrDefault(predicate);
        list.Remove(item);
    }
}

public delegate void CancelRoutedEventHandler(object sender, CancelRoutedEventArgs e);

public class CancelRoutedEventArgs : RoutedEventArgs
{
    
public bool Cancel{get; set;}
}

Posted in WPF | Tagged: , | 4 Comments »

Connection pooling is good

Posted by K. M. on August 9, 2008

I was writing a project which involved accessing user instances of a SQL Server database from two separate processes in turn. This failed with “Login failed” error messages. Connection pooling meant that the process which attached the database first did not close its connection even after its use of the database was over. A simple solution seemed to be to disable connection pooling on both processes. This worked in the sense that there were no exceptions. But performance took a huge hit. Finally I changed the design so that all the work was done in a single process. I could afford to do it since there was not much code involved. This is something to keep in mind when using user instances in SQL Server. Disabling connection pooling is an unacceptable solution.

Posted in SQL Server | Tagged: | Leave a Comment »

Routed Events in WPF

Posted by K. M. on August 9, 2008

Unlike the concept of dependency properties, the concept of routed events is a little difficult to understand. It is clear what purposes dependency properties serve. But routed events? Why would anyone want events to route up or down the element tree? There is a common example given of a Panel with multiple Buttons in it where one can attach an event handler for Button.Click on the Panel instead of on the Buttons and have it called when any Button is clicked. But that does not seem to be a good enough reason to architect an entire feature. And indeed it is not. Event routing becomes much more useful in the context of commands and control templating. It would be much more difficult to write controls like the ScrollBar or the Slider without routed commands (which work via routed events) while allowing them to be easily templatable. The real purpose of routed events is not to attach handlers on some node of a static tree of elements to handle events on the children. It is to enable parent elements to define behavior when a particular event occurs on a child element that it does not statically know of.

Posted in WPF | Tagged: | Leave a Comment »

 
Follow

Get every new post delivered to your Inbox.