python.netwpfironpythonpython.net

Pythonnet Error: XamlParseException : 'Failed to create a 'Click' from the text


I was working on IronPython before (with WPF for developing some GUIs), and recently I started to try pythonnet.

But I found that the xaml file that worked on IronPython does not work on CPython + pythonnet. In IronPython, I can define a Button.Click in the xaml file, but it seems not possible in CPython. I have tried to look up for the answers, but nothing relevant was found. So hopefully you could save me here...

Here is my main script:

import clr
clr.AddReference(r"wpf\PresentationFramework")
from System.IO import StreamReader
from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.Threading import Thread, ThreadStart, ApartmentState

class MyWindow(Window):
    def __init__(self):
        stream = StreamReader('test.xaml')
        window = XamlReader.Load(stream.BaseStream)
        Application().Run(window)

    def Button_Click(self, sender, e):
        print('Button has clicked')

if __name__ == '__main__':
    thread = Thread(ThreadStart(MyWindow))
    thread.SetApartmentState(ApartmentState.STA)
    thread.Start()
    thread.Join()

And here is the test.xmal:

<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="WpfApplication1" Height="300" Width="300"> 
    <Grid>
        <Button x:Name="BUTTON" Content="Button" HorizontalAlignment="Left" Margin="101,82,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click" Background="#FFFF1616"/>
    </Grid>
</Window> 

The error message I got is:

Unhandled Exception: Python.Runtime.PythonException: XamlParseException : 'Failed to create a 'Click' from the text 'Button_Click'.' Line number '6' and line position '132'.

Strangly if I load the same xaml and keep the same Class structure in IronPython, the script works just fine:

import wpf
from System.Windows import Application, Window

class MyWindow(Window):
    def __init__(self):
        self.ui = wpf.LoadComponent(self, 'test.xaml')

    def Button_Click(self, sender, e):
        print('Button has clicked')

if __name__ == '__main__':
    Application().Run(MyWindow())

Thank you very much for help!


Solution

  • you cannot import wpf in python using python.net package. But you can load the XAML file using PresentationFramework, even though presentationframework under python.net has some limitation. The XamlReader can't perform event handling, so we can manually add the event for that button in the python file, not in the XAML file.

    here the gui.xaml code

    <Window 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           Title="WpfApplication1" Height="300" Width="300">
        <Grid>
            <Label Content="Hello world" HorizontalAlignment="Left" Height="32" Margin="65,77,0,0" VerticalAlignment="Top" Width="119"/>
            <Button Name="button1" Content="Button" HorizontalAlignment="Left" Margin="65,145,0,0" VerticalAlignment="Top" Width="75"/>
        </Grid>
    </Window> 
    

    Then here the python code

    import clr
    clr.AddReference("wpf\PresentationFramework") 
    from System.IO import *
    from System.Windows.Markup import XamlReader
    from System.Windows import *
    from System.Threading import Thread, ThreadStart, ApartmentState
    from System.Windows.Controls import *
    
    class MyWindow(Window):
        def __init__(self):
            try:
                stream = StreamReader('gui.xaml')
                self.window = XamlReader.Load(stream.BaseStream)
                ButtoninXAML = LogicalTreeHelper.FindLogicalNode(self.window, "button1") 
                ButtoninXAML.Click +=  RoutedEventHandler(self.Button_Click)
                Application().Run(self.window)      
            except Exception as ex:
                print(ex)
        def Button_Click(self, sender, e):
            print('clicked')
            
            
    if __name__ == '__main__':
        thread = Thread(ThreadStart(MyWindow))
        thread.SetApartmentState(ApartmentState.STA)
        thread.Start()
        thread.Join()