问题
What am I missing? I was trying to implement converters to my XAML-based PowerShell script but with no luck. I've picked up pieces of information from sites like StackOverflow. but couldn't find one successful implementation of a converter in powershell XAML-based GUI script.
in the code I am testing the converter, and it works (you can see 2 examples for conversion) so that means that powershell itself accepted the new converter type, buy this converter cannot be implemented in my xaml code.
$src = @'
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
namespace MyProject
{
public class DemoConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return "kuku";
}
else
{
return "bobo";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
'@
Add-Type -AssemblyName PresentationFramework
Add-Type -TypeDefinition $src -ReferencedAssemblies PresentationFramework
#Checking that the new type works and convert is done...
$c = new-object MyProject.DemoConverter
$c.Convert("gg", $null, $null, $null)
$c.Convert(55, $null, $null, $null)
#Now declaring and loading the xaml
[xml]$XAML = @'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cnv="clr-namespace:MyProject" >
<Window.Resources>
<cnv:DemoConverter x:Key="TestConverter" />
</Window.Resources>
<Grid>
<TextBox x:Name="txtTestValue" Text="I'm here to show that xaml loading works!" />
</Grid>
</Window>
'@
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
$Window.ShowDialog() | out-null
I keep getting this error:
Exception calling "Load" with "1" argument(s): "Cannot create unknown type '{clr-namespace:MyProject}DemoConverter'."
If I remove the line: <cnv:DemoConverter x:Key="TestConverter" />
It will not give the above error and the window will show (but of course, convertion in xaml will not be available), so I guess I'm doing something wrong with namespace and/or assembly deceleration that XAML doesn't like.
Note that on my xaml I'm not yet using the converter. I just want to overcome the above error before trying to use the converter.
Thank you so much in advance!
回答1:
I signed up for Stackoverflow just so I could participate in this question.
I searched for HOURS for a solution to this before I ran across this question here, and then my hopes were dashed to see it's been sitting out here almost a year with no answer.
I had found this related question earlier, and it gives the basic solution we're trying to implement with this question, but doesn't give any details on making it actually work. How to use IValueConverter from powershell?
Luckily, after several more hours, and piecing together a lot of other info, I finally solved it!
There are 3 pieces to it.
- The converter type being added via the C# code is not part of the same namespace of the form, even if you specify the form's namespace in the C# class code. So an extra xmlns: line is needed to include the converter type's namespace in the form. Then when defining the converter in the form resources, you reference that namespace instead of the form's own namespace (usually "local", but I see your example you used "cnv"). Also though, for the reason below, you want your namespace in your C# code to be different from your form's own namespace. (I don't know for sure, but the system might get confused having 2 different namespaces that are really physically separate.)
- BUT, even with the added xmlns: line to include the converter's namespace, the form still can't find it. This is becuase, I discovered, the added converter type is also added to a different Assembly. Apparently when adding custom types with "Add-Type -TypeDefinition", PowerShell creates its own in-memory Assembly and adds the type to that. So, in the added xmlns: line to include the converter's namespace, we also have to specify the assembly for the custom converter type.
- BUT, BUT... What's worse is that this "made-up" Assembly has a randomly generated name, AND that Assembly name is different every time you run the script. (I could not find this fact documented anywhere. I discovered it entirely by accident.) So how can you include an assembly name if you don't know the name? Basically do what you did, create an instance of your new converter object in a variable, then check the properties of that new object variable to determine the Assembly name. Then use string Replace to update your XAML string with the Assembly name.
SO, see the updated code I posted which has the following changes. As the original poster indicated, this example doesn't show the converter in action, but the script will at least run and display the form without error, indicating the the form itself accepted the converter definition and usage in the TextBox.
- Changed namespace in the C# code to MyConverter so it's different from the form's MyProject namespace.
- In the line that creates the $c instance of the converter object, I changed the namespace to MyConverter instead of MyProject, to match the new namespace as above.
- Added $AssemblyName variable to pull out the random assembly name. (I also commented out the calls to Convert and ConvertBack.)
- Changed the Here-String for the XAML code to a string variable, $inputXML so we can use Replace on it. Will convert to xml document later.
- xlmns:Converter line added to include MyConverter namespace. Note the ";assembly=myassembly" piece. The "myassembly" name is simply aplaceholder for the string Replace to easily find later.
- In the declaration of the converter Window.Resources, changed the "cnv:" to "Converter:" to match the Converter type's namespace declaration.
- Added another TextBox to show including the Converter and that the form build accepts this.
- Added [xml]$XAML = $inputXML.... line to convert to xml and replace with the actual assembly name.
Code
$src = @'
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
namespace MyConverter
{
public class DemoConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return "kuku";
}
else
{
return "bobo";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
'@
Add-Type -AssemblyName PresentationFramework
Add-Type -TypeDefinition $src -ReferencedAssemblies PresentationFramework
#Checking that the new type works and convert is done...
$c = new-object MyConverter.DemoConverter
$AssemblyName = $c.gettype().Assembly.FullName.Split(',')[0]
#$c.Convert("gg", $null, $null, $null)
#$c.Convert(55, $null, $null, $null)
#Now declaring and loading the xaml
$inputXML = @'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converter="clr-namespace:MyConverter;assembly=myassembly"
xmlns:cnv="clr-namespace:MyProject" >
<Window.Resources>
<Converter:DemoConverter x:Key="TestConverter" />
</Window.Resources>
<Grid>
<TextBox x:Name="txtTestValue" Text="I'm here to show that xaml loading works!" />
<TextBox x:Name="txtTestValue2" Text="{Binding Path=Whatever, Converter={StaticResource TestConverter}}" />
</Grid>
</Window>
'@
[xml]$XAML = $inputXML -replace 'myassembly', $AssemblyName
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
$Window.ShowDialog() | out-null
来源:https://stackoverflow.com/questions/56816597/error-trying-to-implement-ivalueconverter-in-powershell-xaml-gui-script