.NET 4.0 Breaking Change - Using a Markup Extension as Value of Property Setter in XAML Style

Proposed .NET 4.0 Breaking Change - Using a Markup Extension as Value of Property Setter in XAML Style

  • Monday, January 10, 2011 7:27 PM
     
      Has Code

    In .NET 3.5SP1 I had a style with the following property setting. 

     <Setter Property="ToolTip" Value="{resource:Resource Group=Text, Key=BTN_CLOSE}"/>
    

    The markup extension will access our satellite assemblies and pull in the proper resource text for the tooltip. The reason this is done this way is our application is customizable. Using the markup extension allows our clients to use a key combination to see the Key for the text and then go modify the text value if they choose to do so.

    After upgrading to .NET 4.0 the error below occurs when the above xaml to define the tooltip on my button style is defined. Is there a different way to do this?

    System.Windows.Markup.XamlParseException: A 'Binding' cannot be set on the 'Value' property of type 'Setter'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

    at System.Windows.Markup.XamlReader.RewrapException(Exception e, Uri baseUri)

    at System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlReader templateReader, XamlObjectWriter currentWriter)

    at System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlObjectWriter objectWriter)

    at System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(DependencyObject container, IComponentConnector componentConnector, IStyleConnector styleConnector, List`1 affectedChildren, UncommonField`1 templatedNonFeChildrenField)

    at System.Windows.FrameworkTemplate.LoadContent(DependencyObject container, List`1 affectedChildren)

    at System.Windows.StyleHelper.ApplyTemplateContent(UncommonField`1 dataField, DependencyObject container, FrameworkElementFactory templateRoot, Int32 lastChildIndex, HybridDictionary childIndexFromChildID, FrameworkTemplate frameworkTemplate)

    at System.Windows.FrameworkTemplate.ApplyTemplateContent(UncommonField`1 templateDataField, FrameworkElement container)

    at System.Windows.FrameworkElement.ApplyTemplate()

    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)

    at System.Windows.UIElement.Measure(Size availableSize)

    at System.Windows.Controls.Canvas.MeasureOverride(Size constraint)

    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)

    at System.Windows.UIElement.Measure(Size availableSize)

    at System.Windows.Controls.Grid.MeasureCell(Int32 cell, Boolean forceInfinityV)

    at System.Windows.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV)

    at System.Windows.Controls.Grid.MeasureOverride(Size constraint)

    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)

    at System.Windows.UIElement.Measure(Size availableSize)

    at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)

    at System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)

    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)

    at System.Windows.UIElement.Measure(Size availableSize)

    at System.Windows.Documents.AdornerDecorator.MeasureOverride(Size constraint)

    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)

    at System.Windows.UIElement.Measure(Size availableSize)

    at System.Windows.Controls.Border.MeasureOverride(Size constraint)

    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)

    at System.Windows.UIElement.Measure(Size availableSize)

    at System.Windows.Window.MeasureOverrideHelper(Size constraint)

    at System.Windows.Window.MeasureOverride(Size availableSize)

    at System.Windows.FrameworkElement.MeasureCore(Size availableSize)

    at System.Windows.UIElement.Measure(Size availableSize)

    at System.Windows.Interop.HwndSource.Process_WM_SIZE(UIElement rootUIElement, IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam)

    at System.Windows.Interop.HwndSource.LayoutFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)

    at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)

    at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)

    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)

    at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)

All Replies

  • Monday, January 10, 2011 7:54 PM
     
      Has Code

    I wrote the following code which failed to reproduce the error you mentioned. It shows a button with text "Hello" as expected.

      public class HelloExtension : MarkupExtension
      {
        public HelloExtension()
        {
        }
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
          return "Hello";
        }
      }
    
      <Window.Resources>
        <Style TargetType="Button">
          <Setter Property="Content" Value="{local:Hello}" />
        </Style>
      </Window.Resources>
      <Grid>
        <Button></Button>
      </Grid>
    
    Could you provide a simpified version of your code which shows the error?
  • Monday, January 10, 2011 9:54 PM
     
      Has Code

    I guess I left out a key element. In order to allow the swapping between text and the keys we have a ResourceCache class which stores the values. In the provide value the part that is breaking the tooltip binding is the fact that we are returning binding.ProvideValue(serviceProvider). This is the same as we had it in .NET 3.5SP1.

     public override object ProvideValue(IServiceProvider serviceProvider)
        {
          ResourceCache resourceCache = ResourceCache.Instance;
          resourceCache["cacheKey"] = "hello";
    
          Binding binding;
    
          binding = new Binding();
          binding.Source = resourceCache;
          binding.Path = new PropertyPath("[" + "cacheKey" + "]");
          binding.Mode = BindingMode.OneWay;
    
          return binding.ProvideValue(serviceProvider);
    
        }
  • Tuesday, January 11, 2011 3:12 AM
    Moderator
     
     

    Hi Tammy,

    I can reproduce this issue, I compare the serviceProvider variable in .Net 3.5 and 4:

    .Net 4 redesigns the serviceProvider, and uses to an internal class instead of the Markup.ProvideValueServiceProvider in default at run-time. It may be a issue of .Net 4, that the serviceProvider dose not get the correct target property. I will send the consult internal, and any feedback I will update here. Meanwhile, youc ould submit a feedback on the Microsoft Connect site, our developers will aware of this issue. Thanks.

    Below is my simple test code, it can work in .Net 3.5, but not in .Net 4: http://cid-51b2fdd068799d15.office.live.com/self.aspx/.Public/TestCase/20110111%5E_CustomMarkupExtensionIssue%5E_TestCase.zip

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

  • Friday, January 21, 2011 6:30 PM
     
     

    I'm having this problem as well,  and it will stop us from upgrading.

     

    Here is a link to Tammy's Connect submission

  • Saturday, February 12, 2011 1:12 AM
     
     Proposed Has Code

    I've found the following workaround when facing a similar problem. Try and see if the target object is a setter and if it is just return the binder instead of the provided value.

     

    Not the cleanest solution, but works till a fix comes.

    So according to your example this fix would work:

    ResourceCache resourceCache = ResourceCache.Instance;
              resourceCache["cacheKey"] = "hello";
    
              Binding binding;
              binding = new Binding();
              binding.Source = resourceCache;
              binding.Path = new PropertyPath("[" + "cacheKey" + "]");
              binding.Mode = BindingMode.OneWay;
    
              IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
    
              if (target != null)
              {
                if (target.TargetObject is Setter)
                {
                  return binding;
                }
              }
    
              return binding.ProvideValue(serviceProvider);

    • Edited by SimonLanthier Saturday, February 12, 2011 1:15 AM Code error
    • Proposed As Answer by SimonLanthier Saturday, February 12, 2011 1:21 AM
    •  
  • Monday, February 14, 2011 4:47 PM
     
     
    This worked for me. Thanks!
  • Monday, February 14, 2011 4:50 PM
    Moderator
     
     
    That is greate, thanks Simon for sharing thi s workaround!
    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

  • Friday, May 11, 2012 11:46 AM
     
     

    Thank you so much Simon.

    I was very alarmed to see this is STILL an issue with .NET 4 over a year later, and was extremely grateful to find your workaround.