Embedded IronPython scripts and converting types - wpf

I've got a WPF application that embeds IronPython to use as a scripting language. I've got an object model that IronPython scripts can use to do 'stuff'.
However I've come across a strange problem that I've solved in a way that I don't believe is correct.
In my script I want to type the following to set the location of an object in WPF.
map.CanvasLocation = 10,10
This comes up with an exception saying that it cannot convert from PythonTuple to System.Windows.Point.
I've currently solved that using a custom type converter in my c# object, but I'm not sure if this is the best way to do it.
Is there a way to tell IronPython or .Net in general how to convert from one type to another that can be extended at run time?

The way I do this is to use TypeDescriptor to add a type converter attribute to PythonTuple at runtime.
TypeDescriptor.AddAttributes(typeof(PythonTuple),
new TypeConverterAttribute(typeof(Converter)));
Then I use the following code to find the converter in the attribute setter (SetMemberAfter method)
var converter = TypeDescriptor.GetConverter(value);
if (converter.CanConvertTo(destinationType))
{
var destinationValue = converter.ConvertTo(value, destinationType);
return destinationValue;
}
else
{
throw new InvalidOperationException("Cannot convert from {0} to {1}".UIFormat(
value == null ? "null" : value.GetType().Name, destinationType.Name));
}

Why not do a
map.CanvasLocation = Point(10,10)

Related

WPF two-way binding with internal setter

I'm using WPF's two-way binding on a CLR property, which implements INotifyPropertyChanged.
The set for the property is internal, while the get is public.
Unfortunately, I get the following error:
System.Windows.Markup.XamlParseException was unhandled
Message: An unhandled exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: A TwoWay or OneWayToSource binding cannot work on the read-only property 'Name' of type 'MyType'.
Is this the expected behavior? I would have thought that internal setters should work just fine...
Note that the CLR-type is defined in another assembly, and are visible in the current assembly, with the [assembly: InternalsVisibleTo("MyAssembly")] attribute.
Does anyone have workarounds/suggestions? The declaring assembly is a class library, so it's not an option for me to change the set to public.
You can create your own NEW public wraper property and use getter and setter of it to interact with your internal property
internal string _SideTabHeader;
public string SideTabHeader
{
get { return _SideTabHeader; }
set
{
if( value<0)
{
do nothing
}
else
{
_SideTabHeader=value;
};
}
}
Oh my... I just found out, WPF bindings don't work with internal properties. Oh, Microsoft... Whatever were you thinking?
Update:
Here's what I've understood so far (Thank you, #Grx70):
WPF is not a native part of the .NET framework, it's just a "plug-in" framework that happens to be also written by Microsoft. That is why it can't access the internal members of your assembly.
Microsoft could have allowed WPF to respect the [assembly: InternalsVisibleTo("XXX")] attribute, but as of right now, WPF ignores it - which unfortunately does not leave one with any easy workarounds.
Note: I tested using InternalVisibleTo - both Signed and Unsigned, with PresentationFramework, PresentationCore, and a whole bunch of other DLLs with no luck.
The only workaround I can think of right now is to create a "Proxy" class which can expose all required members as public. This is quite a PITA (I have a LOT of classes, and I hate the maintenance nightmare that comes with creating an equal number of "Proxy" classes) - so I might look into using PostSharp, or Fody or some kind of weaver to auto-create these "Proxy" classes if I can.
All the best to anyone else facing this issue.
This is very late and not solving the initial question, but as very related it may help someone else which very similar problem...
If your internal property is of type Enum else skip
In my case I was trying to do a WPF xaml binding to a property of type inherited from a WCF service. The easy way to solve that simple case was to use int.
public Dictionary<int, string> ProductsList => EnumExtensions.ProductsList;
public int ProductType
{
get { return (int)_DeliveryProduct.ProductType; }
set
{
if (value.Equals(ProductType)) return;
_DeliveryProduct.ProductType = (ProductEnum)value;
RaisePropertyChanged(() => ProductType);
}
}
_DeliveryProduct is my reference to my domain object for which the property ProductType is an enum but in my viewmodel that property is an int.
... Note that ProductEnum is autogenerated from the API and can't be changed to public.
internal static Dictionary<int, string> ProductsList => new Dictionary<int, string>
{
{(int)ProductEnum.Regular, ProductEnum.Regular.GetDisplayName()},
{(int)ProductEnum.Intermediate, ProductEnum.Intermediate.GetDisplayName()},
{(int)ProductEnum.Super, ProductEnum.Super.GetDisplayName()},
{(int)ProductEnum.Diesel, ProductEnum.Diesel.GetDisplayName()}
};

Resolve generic types with IXamlTypeResolver

I'd written a new TypeExtension class that replace the default System.Windows.Markup.TypeExtension to handle generic types. It allows the Xaml to work with generic types in many different ways, like :
<DataTemplate DataType="{ck:Type [here a generic type name]}">
This is how it works :
I'd written TypeExtention class, that inherit of MarkupExtension (it's the ck:Type in the example)
I'd override the ProvideValue method to call Resolve on IXamlTypeResolver
IXamlTypeResolver service = p.GetService( true );
_type = service.Resolve( _typeName );
So, in .NET 3.5, I can resolve types like "local:IConfigItemProperty`1". But now in .NET 4 (with WPF 4) the resolve method throws an exception :
Character '' was unexpected in string 'local:IConfigItemProperty1'. Invalid XAML type name.
Do you think .NET 4.0 does not support '`' anymore ?
While the post is a bit old, i put the answer for any other dev looking for solution.
It's seem from 4.0 they change the way the Reader deal with generic. The lack of support to '`' by the IXamlTypeResolver service seem to be a bug while the regression is not documented at all.
If u want to use generic, you might want to switch with x:TypeArguments which is list the argument for the generic.
C#
public class GraphBase<N,L,P> : IGraph<N,L,P>
{
...
}
XAML
<?xml version="1.0" encoding="utf-8" ?>
<GraphBase
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="clr-namespace:MyGeoLive.Topology;assembly=MyGeoLive.Topology"
xmlns:System="clr-namespace:System;assembly=mscorlib"
x:TypeArguments="System:String,System:String,System:String" >
</GraphBase>

Error Application cast in WPF

i have 2 projects in my solution (main is A.WPF and secondary is B.WPF)
when i'm trying to access variables inside my App.xaml.cs in B.WPF:
filename = ((App)Application.Current).ErrorLogFileName;
i get the following error:
Unable to cast object of type 'A.App' to type 'B.App'.
i also tried the following:
filename = ((B.App)Application.Current).ErrorLogFileName;
but still the same error...
the definition in B.App is:
private string _errorLogFileName = "error log.xml";
public string ErrorLogFileName
{
get { return _errorLogFileName; }
}
please assist...
Looks like you need to do:
filename = ((A.App)Application.Current).ErrorLogFileName;
The error is saying the type is A.App, yet in both cases you are trying to cast to B.App.
There can only be one current application also.
Application.Current refers to the current application. The only way to be allowed to cast the current App to another App-type is when the other App-type is a base class of the current App-type.
Are A.App and B.App siblings or is B.App a base class of A.App?
If you don't want B to have a reference to A (or can't as you want A to reference B and that would cause a circular reference), then you need a common type defined in a third assembly that both A and B reference. In our implementation we tend to have a ConfigurationData type that is in a separate project referenced by both Wpf projects, e.g.
public static class ConfigurationData
{
private static string _errorLogFileName = "error log.xml";
public string ErrorLogFileName
{
get { return _errorLogFileName; }
}
}
Another approach would be to define an Interface for your ErrorLogFileName property in a 3rd assembly that both A and B reference, and then implement that interface on your Wpf Application class - A and B would then both be able to cast to that type. If you wanted your A project to set the values on that at runtime, you could make the ErrorLogFileName a read-write property instead and initialize it in your application startup.
I personally prefer using a separate ConfigurationData type from the Wpf app object for this kind of stuff (ErrorLogFileName etc.) as it can then also be used for code that might execute in a unit test and therefore might not be running under a Wpf application - it also avoids having to do casts all over the place (ConfigurationData.ErrorLogFileName instead of ((IAppConfigurationData)Application.Current).ErrorLogFileName.
BTW, if you have an Application object in both assemblies it sounds like you might have both assemblies configured to build as Output type: Windows Application in your project properties. You should only really have one assembly that is configured as the Windows Application and the rest should be Class Library to avoid confusing numbers of Application classes being generated - only the one in the main EXE (and it's related resources) will get created at runtime.

Silverlight InlineCollection.Add(InlineUIContainer) missing?

I'm having difficulty adding the inline of specific type InlineUIContainer into the InlineCollection (Content property) of a TextBlock. It appears the .Add() method of InlineCollection doesn't accept this type, however you can clearly set it through XAML without explicitly marking the content as a InlineContainer, as demonstrated in many examples:
http://msdn.microsoft.com/en-us/library/system.windows.documents.inlineuicontainer.aspx
Is it possible to programatically add one of these as in the following?
Target.Inlines.Add(new Run() { Text = "Test" });
Target.Inlines.Add(new InlineUIContainer() {
Child = new Image() { Source = new BitmapImage(new Uri("http://example.com/someimage.jpg")) } });
Target.Inlines.Add(new Run() { Text = "TestEnd" });
I have a feeling what's going on is that Silverlight is using a value converter to create the runs when specified in XAML as in the example which doesn't use InlineContainer, but I'm not sure where to look to find out.
The specific error I'm getting is as follows:
Cannot add value of type 'System.Windows.Documents.InlineUIContainer' to a 'InlineCollection' in a 'System.Windows.Controls.TextBlock'.
As pointed out by Jedidja, we need to use RichTextBox to do this in Silverlight.
You can't Add() Runs directly, but you can add Spans containing Runs.
Interestingly, you can also do this:
textBlock.Inlines.Clear();
textBlock.Inlines.Add(new Span());
textBlock.Inlines[0] = new Run();
Not that it's a good idea to hack around what the framework is actively trying to prevent you from doing.
P.S. If you can't figure out what XAML is doing, inspect the visual tree.

System.Reflection.AmbiguousMatchException

I added Designer support for my control.
I got the following exception when setting the property value like below.
var colStyle = visibleColumn.Properties["PropertyName"].SetValue(Value);
The same above code works fine for VS 2010 project
but it shows the following exception for VS 2008 project
InnerException: System.Reflection.AmbiguousMatchException
Message="Ambiguous match found."
Source="mscorlib"
Just a wild guess, but this may be due to a derived class having a property that is hiding the base class implementation, confusing the reflection mechanism.
If you use GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).SetValue(target, value, null) it may fix the problem.

Resources