View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0003128 | NoesisGUI | C# SDK | public | 2024-02-27 18:27 | 2024-02-29 10:37 |
| Reporter | samc | Assigned To | maherne | ||
| Priority | normal | Severity | minor | ||
| Status | resolved | Resolution | fixed | ||
| Product Version | 3.2.3 | ||||
| Target Version | 3.2.4 | ||||
| Summary | 0003128: Exceptions in visual studio blend designer | ||||
| Description | More details here: There are exceptions when ProvideValue is called for LocExtension when a template it is in is being loaded, which then trickle down to the designer. | ||||
| Platform | Windows | ||||
|
Thanks for the report. I have attached a WPF LocExtension.cs with the fix, if you'd like to test it locally, and we will update the NoesisGUI.Extensions Nuget package with this fix in the next 24 hours. LocExtension.cs (9,673 bytes)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Markup;
namespace NoesisGUIExtensions
{
/// <summary>
/// Implements a markup extension that supports references to a localization ResourceDictionary.
///
/// Provides a value for any XAML property attribute by looking up a reference in the
/// ResourceDictionary defined by the Source attached property. Values will be re-evaluated when
/// the Source attached property changes.
///
/// If used with a string or object property, and the provided resource key is not found, the
/// LocExtension will return a string in the format "<Loc !%s>" where %s is replaced with the key.
///
/// This example shows the full setup for a LocExtension. It utilizes the RichText attached
/// property to support BBCode markup in the localized strings. The Loc.Source property references
/// the "Language_en-gb.xaml" ResourceDictionary below.
///
/// Usage:
///
/// <StackPanel
/// xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
/// xmlns:noesis="clr-namespace:NoesisGUIExtensions"
/// noesis:Loc.Source="Language_en-gb.xaml">
/// <Image Source="{noesis:Loc Flag}"/>
/// <TextBlock noesis:RichText.Text="{noesis:Loc TitleLabel}"/>
/// <TextBlock noesis:RichText.Text="{noesis:Loc SoundLabel}"/>
/// </StackPanel>
///
/// This is the contents of a "Language_en-gb.xaml" localized ResourceDictionary:
///
/// Usage:
///
/// <ResourceDictionary
/// xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
/// xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
/// xmlns:sys="clr-namespace:System;assembly=mscorlib">
/// <ImageBrush x:Key="Flag" ImageSource="Flag_en-gb.png" Stretch="Fill"/>
/// <sys:String x:Key="TitleLabel">[b]LOCALIZATION SAMPLE[/b]</sys:String>
/// <sys:String x:Key="SoundLabel">A [i]sound[/i] label</sys:String>
/// </ResourceDictionary>
///
/// </summary>
[ContentProperty("ResourceKey")]
public class LocExtension : MarkupExtension
{
private string _resourceKey;
public LocExtension()
{
}
public LocExtension(string resourceKey)
{
_resourceKey = resourceKey;
}
/// <summary>
/// Gets or sets the key to use when finding a resource in the active localization ResourceDictionary.
///
/// Usage:
///
/// <Grid
/// xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
/// xmlns:noesis="clr-namespace:NoesisGUIExtensions"
/// noesis:Loc.Source="Locale_fr.xaml">
/// <TextBlock Text="{noesis:Loc Title}"/>
/// <Image Source="{noesis:Loc IntroBackground}"/>
/// </Grid>
///
/// </summary>
public string ResourceKey
{
get { return _resourceKey; }
set { _resourceKey = value; }
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget valueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueTarget == null || !(valueTarget.TargetObject is DependencyObject target))
{
return this;
}
DependencyProperty targetProperty = (DependencyProperty)valueTarget.TargetProperty;
LocMonitor monitor = (LocMonitor)target.GetValue(MonitorProperty);
if (monitor == null)
{
monitor = new LocMonitor(target);
target.SetValue(MonitorProperty, monitor);
}
return monitor.AddDependencyProperty(targetProperty, _resourceKey);
}
#region Source attached property
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached(
"Source",
typeof(Uri),
typeof(LocExtension),
new PropertyMetadata(null, SourceChangedCallback)
);
private static void SourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Uri source = (Uri)d.GetValue(SourceProperty);
if (source == null)
{
d.SetValue(ResourcesProperty, null);
return;
}
ResourceDictionary resourceDictionary = new ResourceDictionary
{
Source = source
};
d.SetValue(ResourcesProperty, resourceDictionary);
}
public static Uri GetSource(UIElement target)
{
return (Uri)target.GetValue(SourceProperty);
}
public static void SetSource(UIElement target, Uri value)
{
target.SetValue(SourceProperty, value);
}
#endregion
#region Resources attached property
public static readonly DependencyProperty ResourcesProperty =
DependencyProperty.RegisterAttached(
"Resources",
typeof(ResourceDictionary),
typeof(LocExtension),
new FrameworkPropertyMetadata(null, flags: FrameworkPropertyMetadataOptions.Inherits, ResourcesChangedCallback)
);
private static void ResourcesChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LocMonitor monitor = (LocMonitor)d.GetValue(MonitorProperty);
if (monitor != null)
{
if (monitor.TargetObject != d)
{
monitor = monitor.Clone(d);
d.SetValue(MonitorProperty, monitor);
}
monitor.InvalidateResources((ResourceDictionary)e.NewValue);
}
}
public static ResourceDictionary GetResources(DependencyObject dependencyObject)
{
return (ResourceDictionary)dependencyObject.GetValue(ResourcesProperty);
}
public static void SetResources(DependencyObject dependencyObject, ResourceDictionary resources)
{
dependencyObject.SetValue(ResourcesProperty, resources);
}
#endregion
#region Monitor attached property
private static readonly DependencyProperty MonitorProperty =
DependencyProperty.RegisterAttached(
".Monitor",
typeof(LocMonitor),
typeof(LocExtension),
new PropertyMetadata(null)
);
#endregion
}
internal class LocMonitor
{
private readonly List<Tuple<DependencyProperty, string>> _monitoredDependencyProperties =
new List<Tuple<DependencyProperty, string>>();
public DependencyObject TargetObject { get; }
public LocMonitor(DependencyObject targetObject)
{
TargetObject = targetObject;
}
public object AddDependencyProperty(DependencyProperty targetProperty, string resourceKey)
{
ResourceDictionary resourceDictionary = LocExtension.GetResources(TargetObject);
for (int i = 0; i < _monitoredDependencyProperties.Count; i++)
{
if (_monitoredDependencyProperties[i].Item1 == targetProperty)
{
_monitoredDependencyProperties[i] =
new Tuple<DependencyProperty, string>(_monitoredDependencyProperties[i].Item1, resourceKey);
return Evaluate(targetProperty, resourceKey, resourceDictionary);
}
}
_monitoredDependencyProperties.Add(new Tuple<DependencyProperty, string>(targetProperty, resourceKey));
return Evaluate(targetProperty, resourceKey, resourceDictionary);
}
public void InvalidateResources(ResourceDictionary resourceDictionary)
{
foreach (Tuple<DependencyProperty, string> entry in _monitoredDependencyProperties)
{
TargetObject.SetValue(entry.Item1,
Evaluate(entry.Item1, entry.Item2, resourceDictionary));
}
}
public LocMonitor Clone(DependencyObject targetObject)
{
LocMonitor clone = new LocMonitor(targetObject);
clone._monitoredDependencyProperties.AddRange(_monitoredDependencyProperties);
return clone;
}
private static object Evaluate(DependencyProperty targetProperty, string resourceKey,
ResourceDictionary resourceDictionary)
{
if (resourceDictionary != null && resourceDictionary.Contains(resourceKey))
{
object resource = resourceDictionary[resourceKey];
if (resource != null)
{
return resource;
}
Console.WriteLine($"[NOESIS/E] Resource key '{resourceKey}' not found in Loc Resources");
}
if (targetProperty.PropertyType == typeof(string))
{
return $"<Loc !{resourceKey}>";
}
return null;
}
}
}
|
|
|
I discovered that Blend can pass C# Reflection data to ProvideValue during it's IntelliSense process, this was leading to a "Unable to cast object of type 'System.Reflection.RuntimePropertyInfo' to type 'System.Windows.DependencyProperty'" error. This error does not affect anything during design or runtime. I have attached an updated version of WPF LocExtension.cs, which fixes this error from occurring, and this fix will be included in our Nuget package update. LocExtension-2.cs (9,680 bytes)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Markup;
namespace NoesisGUIExtensions
{
/// <summary>
/// Implements a markup extension that supports references to a localization ResourceDictionary.
///
/// Provides a value for any XAML property attribute by looking up a reference in the
/// ResourceDictionary defined by the Source attached property. Values will be re-evaluated when
/// the Source attached property changes.
///
/// If used with a string or object property, and the provided resource key is not found, the
/// LocExtension will return a string in the format "<Loc !%s>" where %s is replaced with the key.
///
/// This example shows the full setup for a LocExtension. It utilizes the RichText attached
/// property to support BBCode markup in the localized strings. The Loc.Source property references
/// the "Language_en-gb.xaml" ResourceDictionary below.
///
/// Usage:
///
/// <StackPanel
/// xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
/// xmlns:noesis="clr-namespace:NoesisGUIExtensions"
/// noesis:Loc.Source="Language_en-gb.xaml">
/// <Image Source="{noesis:Loc Flag}"/>
/// <TextBlock noesis:RichText.Text="{noesis:Loc TitleLabel}"/>
/// <TextBlock noesis:RichText.Text="{noesis:Loc SoundLabel}"/>
/// </StackPanel>
///
/// This is the contents of a "Language_en-gb.xaml" localized ResourceDictionary:
///
/// Usage:
///
/// <ResourceDictionary
/// xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
/// xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
/// xmlns:sys="clr-namespace:System;assembly=mscorlib">
/// <ImageBrush x:Key="Flag" ImageSource="Flag_en-gb.png" Stretch="Fill"/>
/// <sys:String x:Key="TitleLabel">[b]LOCALIZATION SAMPLE[/b]</sys:String>
/// <sys:String x:Key="SoundLabel">A [i]sound[/i] label</sys:String>
/// </ResourceDictionary>
///
/// </summary>
[ContentProperty("ResourceKey")]
public class LocExtension : MarkupExtension
{
private string _resourceKey;
public LocExtension()
{
}
public LocExtension(string resourceKey)
{
_resourceKey = resourceKey;
}
/// <summary>
/// Gets or sets the key to use when finding a resource in the active localization ResourceDictionary.
///
/// Usage:
///
/// <Grid
/// xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
/// xmlns:noesis="clr-namespace:NoesisGUIExtensions"
/// noesis:Loc.Source="Locale_fr.xaml">
/// <TextBlock Text="{noesis:Loc Title}"/>
/// <Image Source="{noesis:Loc IntroBackground}"/>
/// </Grid>
///
/// </summary>
public string ResourceKey
{
get { return _resourceKey; }
set { _resourceKey = value; }
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget valueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueTarget == null
|| !(valueTarget.TargetObject is DependencyObject target)
|| !(valueTarget.TargetProperty is DependencyProperty targetProperty))
{
return this;
}
LocMonitor monitor = (LocMonitor)target.GetValue(MonitorProperty);
if (monitor == null)
{
monitor = new LocMonitor(target);
target.SetValue(MonitorProperty, monitor);
}
return monitor.AddDependencyProperty(targetProperty, _resourceKey);
}
#region Source attached property
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached(
"Source",
typeof(Uri),
typeof(LocExtension),
new PropertyMetadata(null, SourceChangedCallback)
);
private static void SourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Uri source = (Uri)d.GetValue(SourceProperty);
if (source == null)
{
d.SetValue(ResourcesProperty, null);
return;
}
ResourceDictionary resourceDictionary = new ResourceDictionary
{
Source = source
};
d.SetValue(ResourcesProperty, resourceDictionary);
}
public static Uri GetSource(UIElement target)
{
return (Uri)target.GetValue(SourceProperty);
}
public static void SetSource(UIElement target, Uri value)
{
target.SetValue(SourceProperty, value);
}
#endregion
#region Resources attached property
public static readonly DependencyProperty ResourcesProperty =
DependencyProperty.RegisterAttached(
"Resources",
typeof(ResourceDictionary),
typeof(LocExtension),
new FrameworkPropertyMetadata(null, flags: FrameworkPropertyMetadataOptions.Inherits, ResourcesChangedCallback)
);
private static void ResourcesChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LocMonitor monitor = (LocMonitor)d.GetValue(MonitorProperty);
if (monitor != null)
{
if (monitor.TargetObject != d)
{
monitor = monitor.Clone(d);
d.SetValue(MonitorProperty, monitor);
}
monitor.InvalidateResources((ResourceDictionary)e.NewValue);
}
}
public static ResourceDictionary GetResources(DependencyObject dependencyObject)
{
return (ResourceDictionary)dependencyObject.GetValue(ResourcesProperty);
}
public static void SetResources(DependencyObject dependencyObject, ResourceDictionary resources)
{
dependencyObject.SetValue(ResourcesProperty, resources);
}
#endregion
#region Monitor attached property
private static readonly DependencyProperty MonitorProperty =
DependencyProperty.RegisterAttached(
".Monitor",
typeof(LocMonitor),
typeof(LocExtension),
new PropertyMetadata(null)
);
#endregion
}
internal class LocMonitor
{
private readonly List<Tuple<DependencyProperty, string>> _monitoredDependencyProperties =
new List<Tuple<DependencyProperty, string>>();
public DependencyObject TargetObject { get; }
public LocMonitor(DependencyObject targetObject)
{
TargetObject = targetObject;
}
public object AddDependencyProperty(DependencyProperty targetProperty, string resourceKey)
{
ResourceDictionary resourceDictionary = LocExtension.GetResources(TargetObject);
for (int i = 0; i < _monitoredDependencyProperties.Count; i++)
{
if (_monitoredDependencyProperties[i].Item1 == targetProperty)
{
_monitoredDependencyProperties[i] =
new Tuple<DependencyProperty, string>(_monitoredDependencyProperties[i].Item1, resourceKey);
return Evaluate(targetProperty, resourceKey, resourceDictionary);
}
}
_monitoredDependencyProperties.Add(new Tuple<DependencyProperty, string>(targetProperty, resourceKey));
return Evaluate(targetProperty, resourceKey, resourceDictionary);
}
public void InvalidateResources(ResourceDictionary resourceDictionary)
{
foreach (Tuple<DependencyProperty, string> entry in _monitoredDependencyProperties)
{
TargetObject.SetValue(entry.Item1,
Evaluate(entry.Item1, entry.Item2, resourceDictionary));
}
}
public LocMonitor Clone(DependencyObject targetObject)
{
LocMonitor clone = new LocMonitor(targetObject);
clone._monitoredDependencyProperties.AddRange(_monitoredDependencyProperties);
return clone;
}
private static object Evaluate(DependencyProperty targetProperty, string resourceKey,
ResourceDictionary resourceDictionary)
{
if (resourceDictionary != null && resourceDictionary.Contains(resourceKey))
{
object resource = resourceDictionary[resourceKey];
if (resource != null)
{
return resource;
}
Console.WriteLine($"[NOESIS/E] Resource key '{resourceKey}' not found in Loc Resources");
}
if (targetProperty.PropertyType == typeof(string))
{
return $"<Loc !{resourceKey}>";
}
return null;
}
}
}
|
|
|
We just released Noesis.GUI.Extensions 3.0.27 to fix both issues. |
|
|
I have the latest version of the plugin, and now I'm seeing a new exception in the same spot: Severity Code Description Project File Line Suppression State Details I'll try to get some more information about this |
|
|
Adding some screen shots of teh exception - one i caught in the debugger and the other of the way the exception shows up in the designer. |
|
|
One question, is the SessionLabelText a CLR or a DP property? |
|
|
Ok, we've been able to reproduce it. I generated a new version 3.0.28 of the Nuget for you to try. |
|
|
Looks good! feel free to close the bug now |
|
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2024-02-27 18:27 | samc | New Issue | |
| 2024-02-27 18:27 | samc | Tag Attached: C# | |
| 2024-02-27 18:49 | maherne | Note Added: 0009245 | |
| 2024-02-27 18:49 | maherne | File Added: LocExtension.cs | |
| 2024-02-27 18:50 | maherne | Assigned To | => maherne |
| 2024-02-27 18:50 | maherne | Status | new => assigned |
| 2024-02-27 18:50 | maherne | Target Version | => 3.2.4 |
| 2024-02-27 18:51 | maherne | Status | assigned => resolved |
| 2024-02-27 18:51 | maherne | Resolution | open => fixed |
| 2024-02-27 18:51 | maherne | Fixed in Version | => 3.2.4 |
| 2024-02-27 18:52 | maherne | Status | resolved => feedback |
| 2024-02-27 18:52 | maherne | Resolution | fixed => reopened |
| 2024-02-28 12:05 | maherne | Note Added: 0009251 | |
| 2024-02-28 12:05 | maherne | File Added: LocExtension-2.cs | |
| 2024-02-28 12:25 | sfernandez | Status | feedback => resolved |
| 2024-02-28 12:25 | sfernandez | Resolution | reopened => fixed |
| 2024-02-28 12:25 | sfernandez | Fixed in Version | 3.2.4 => |
| 2024-02-28 12:25 | sfernandez | Note Added: 0009252 | |
| 2024-02-28 18:52 | samc | Status | resolved => feedback |
| 2024-02-28 18:52 | samc | Resolution | fixed => reopened |
| 2024-02-28 18:52 | samc | Note Added: 0009258 | |
| 2024-02-28 19:04 | samc | Note Added: 0009259 | |
| 2024-02-28 19:04 | samc | File Added: Screenshot 2024-02-28 100052.png | |
| 2024-02-28 19:04 | samc | File Added: Screenshot 2024-02-28 100354.png | |
| 2024-02-28 19:04 | samc | Status | feedback => assigned |
| 2024-02-28 19:42 | sfernandez | Status | assigned => feedback |
| 2024-02-28 19:42 | sfernandez | Note Added: 0009261 | |
| 2024-02-28 20:59 | sfernandez | Note Added: 0009262 | |
| 2024-02-28 21:08 | samc | Note Added: 0009263 | |
| 2024-02-28 21:08 | samc | Status | feedback => assigned |
| 2024-02-29 10:37 | sfernandez | Status | assigned => resolved |
| 2024-02-29 10:37 | sfernandez | Resolution | reopened => fixed |