Jump to content

Recommended Posts

Posted

Hi @MattyD

16 hours ago, MattyD said:
  • I also installed the msix (this is a runtime afterall!) - I'm not sure if this step is strictly necessary for now. Maybe someone could check?

... I didn't install it, and "WindowTest.au3" works anyway ...

16 hours ago, MattyD said:
  • yes, you can probably the x86 libraries - but I haven't tested them... be sure to alter the "#AutoIt3Wrapper_UseX64" directive in WindowTest.au3 if you plan to go down that path.

... I also tried this stuff but it doesn't work ...   I get this -->  !>01:19:22 AutoIt3.exe ended.rc:-1073741819

Thanks for all this work!  👍

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Posted

Thanks for that Gianni :) I've amended the instructions.

Its interesting that x86 is broken, I'll have to have a look at that at some stage! 

A new test script is now up there, "WindowTest.au3" should be the only modified file.
There's just some initial attempts at handling window messages for the main window.

  • 2 weeks later...
Posted (edited)

Hi all,

Apologies in advance if this is a bit unclear - I'll post a follow up at some point to help demonstrate.

It seems we should be able initialise the runtime a bit better by distributing Microsoft.WindowsAppRuntime.Bootstrap.dll:

firstly install Microsoft.WindowsAppRuntime.1.7.msix (refer to post #1), then in the code:

;From WindowsAppSDK-VersionInfo.h
Global Const $WINDOWSAPPSDK_RELEASE_MAJORMINOR = 0x00010007
Global Const $WINDOWSAPPSDK_RELEASE_VERSION_TAG = ""
Global Const $WINDOWSAPPSDK_RUNTIME_VERSION_UINT64 = 0x1B58020A05A40000 ;"7000.522.1444.0"

Global Enum $MddBootstrapInitializeOptions_None, _
    $MddBootstrapInitializeOptions_OnError_DebugBreak, _
    $MddBootstrapInitializeOptions_OnError_DebugBreak_IfDebuggerAttached, _
    $MddBootstrapInitializeOptions_OnError_FailFast, _
    $MddBootstrapInitializeOptions_OnNoMatch_ShowUI, _
    $MddBootstrapInitializeOptions_OnPackageIdentity_NOOP
    
Global $__g_hDllWinAppRTBootStrap = DllOpen("Microsoft.WindowsAppRuntime.Bootstrap.dll")

Local $aCall = DllCall($__g_hDllWinAppRTBootStrap, "long", "MddBootstrapInitialize2", _
        "uint", $WINDOWSAPPSDK_RELEASE_MAJORMINOR, _
        "wstr", $WINDOWSAPPSDK_RELEASE_VERSION_TAG, _
        "uint64", $WINDOWSAPPSDK_RUNTIME_VERSION_UINT64, _
        "uint", $MddBootstrapInitializeOptions_None)

I believe that ShowUI flag is supposed to bring up a dialog if the runtime doesn't exist... but for me this just crashes the script. The "none" flag does work ok though, and will return a sensible error on failure.

So how does this help?

It means we don't need to manually load the dlls in - and instead of directly calling DllGetActivationFactory on individual Dll files (which was a bit hacky), the normal system call  - RoGetActivationFactory starts to work.  This is important because some objects were failing to create with the "Class not registered" error. I'd say these ones were internally trying to call GetActivationFactory for dependencies etc...

Edit - PS: I've nabbed the bootstrap dll from the Microsoft.WindowsAppSDK nuget package. Its a bit annoying to get, so I've attached it here.

But to get it manually:

Microsoft.WindowsAppRuntime.Bootstrap.dll

Edited by MattyD
Posted (edited)

Hi all,

Ive updated the example with an initial attempt at a button control.

Its non-interactive, and you need to roll over the widow before it appears ATM.  But I'll keep chipping away at it...

I've also updated the instructions in post #1. We're now using the bootstrapper as explained above, so that'll require installing the runtime.
I haven't tested this with a fresh machine, so please let me know if there's issues getting the script to run!

Edit: if you comment out the while loop around line 125, then uncomment this bit below, the button then becomes "clickable".  You will need to force-kill the script to exit though... Looks like more digging into the dispatcher/msg loop is on the cards...

IDispatcherQueue3_RunEventLoop($pDispatchQ)

 

Edited by MattyD
Posted

Hi folks, bit more progress.

You can push the button using a mocked-up delegate. (same technique used for the colorPicker example in the other WinRT thread.)  Its a bit of a manual process for now, but the plan is to build in a few internal funcs to streamline things...

Main points of difference:

  • We have a working button! Dumb error - there was a iWinID filter in place. So the main window messages were getting through, but button messages weren't being processed.
  • We break out of the message loop when our winproc handles WM_DESTROY. Previously I was sending WM_QUIT when the "X" was pressed - i.e. looking for SC_CLOSE in a WM_SYSCOMMAND message.
  • We're now properly closing the dispatch queue with a PostQuitMessage call.  This means we can make use of RunEventLoop() without being stuck in an endless loop :)
  • Last upload broke the class explorer, so that now should be fixed too.

I've attached the two changed files to save people re-downloading the whole zip again...

ClassExplorer v2.3.3.au3 WindowTest.au3

Posted

I appreciate the work that you are doing on this. It's interesting to follow your progress as well.

I have to admit, I really like the look of that button. I had never really thought much of WinRT or WinUI3 before but it's got my attention now.

One interesting thing: You can't see the button with the default Windows Dark theme. You can see the button with Windows Light theme and just about any other theme. But not default Dark theme for some reason. I suppose there must be more to the WinUI3 theming and theme detection but I don't understand it at all yet.

Posted

my pleasure mate :) and thanks for the info. 

From some precursory reading, I think we might have to set a pallet for dark mode by setting the resources property of Microsoft.UI.Xaml.Application.  But take that with a pinch of salt - I'll need to sus it out properly!

  • 3 weeks later...
Posted

Hi guys, apologies in advance for the wall of text!

Just an update as to where things are ATM - I've have gone on a tangent re: resolving IIDs for paramatised interfaces. This is more of a core WinRT thing -  but I thought I'd take whoever is interested along on the ride...

So I attempted to create a grid (Microsoft.UI.Xaml.Controls.Grid) which should theoretically allows us drop other controls within it. Basically its a layout thing to divide up the window. Want an "OK" button down the bottom right? Just nest the button in the cell that is down there!.

Creating a grid, then extracting the row and column definitions is easy enough.

#include "Include\Classes\Microsoft.UI.Xaml.Controls.Grid.au3"

;-- Setup window etc. --

Local $pGrid_Fact = _WinRT_GetActivationFactory("Microsoft.UI.Xaml.Controls.Grid", $sIID_IGridFactory)
Local $pGrid = IGridFactory_CreateInstance($pGrid_Fact, 0, $pInner)

Local $pColDefs = IGrid_GetColumnDefinitions($pGrid)
Local $pRowDefs = IGrid_GetRowDefinitions($pGrid)

Looking at $pColDefs, we can determine its a "Microsoft.UI.Xaml.Controls.ColumnDefinitionCollection". We know this by inspecting Microsoft.UI.Xaml.Controls.Grid in the class explorer. Otherwise _WinRT_DisplayInterfaces($pColDefs ) or _WinRT_DisplayClass($pColDefs ) will get you to the same place.

Method : get_ColumnDefinitions
             Fn : value = get_ColumnDefinitions()
             P0 : value                type: Microsoft.UI.Xaml.Controls.ColumnDefinitionCollection

After digging in the metadata we can resolve some IIDs of $pColDefs, but at best _WinRT_DisplayInterfaces() only gives us:

Class: Microsoft.UI.Xaml.Controls.ColumnDefinitionCollection
{CDFBA81A-54FA-557D-A712-21640F16C534}
{749BC47C-1743-5C21-9CED-C8A1134C7BA7}
{80741C8F-A401-5C63-B6C4-15D165E541C7}
{E7BEAEE7-160E-50F7-8789-D63463F979FA} - IDependencyObject
{EB24C20B-9816-4AC7-8CFF-36F67A118F4E}
{00000038-0000-0000-C000-000000000046} - IWeakReferenceSource
{DF0B3D60-548F-101B-8E65-08002B2BD119} - ISupportErrorInfo
{00000000-0000-0000-C000-000000000046} - IUnknown
{AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90} - IInspectable

But what we're looking for is these collection interfaces.

TypeDef : Microsoft.UI.Xaml.Controls.ColumnDefinitionCollection
        Extends : System.Object
      Interface : Windows.Foundation.Collections.IVector`1<Microsoft.UI.Xaml.Controls.ColumnDefinition>
      Interface : Windows.Foundation.Collections.IIterable`1<Microsoft.UI.Xaml.Controls.ColumnDefinition>

The class explorer tells us IVector should be there. Windows.Foundation.Collections.IVector`1 has a GUID defined as "{913337E9-11A1-4345-A3A2-4E7F956E222D}". so what gives? 

It does not appear in our list because the IVector`1 GUID is NOT an IID, it is something else called a PIID.  It needs to be combined with the datatype in order to give us the IID for Windows.Foundation.Collections.IVector`1<Microsoft.UI.Xaml.Controls.ColumnDefinition>. 

We can probably just assume the top GUID maps to this... but we really need a way to resolve it properly.  The IIDs for paramatiesed interfaces are calculated at runtime so we can't just dig in the metadata for them. Enter RoGetParameterizedTypeInstanceIID. This is the can of worms that I'm still trying to nail down - so stay tuned for part 2!

Once all that is sorted I'll also need to fix the WinRT Library generator. At the moment methods involving arrays don't generate correctly, which is kinda important when dealing with collections!!!

Posted

OK I've managed to get this working for the most part... bear with me.

say we want to resolve: "Windows.Foundation.Collections.IVector`1<Microsoft.UI.Xaml.Controls.ColumnDefinition>"
broadly speaking, this is what needs to happen.

  • Create a MetaDataLocator - this is similar to how we're creating our delegates. It doesn't inherit from IUnknown, and has 1 method called "Locate"... but more on this in a bit!
  • Call RoParseTypeName. This gives us an array of name elements i.e.: ["Windows.Foundation.Collections.IVector`1", "Microsoft.UI.Xaml.Controls.ColumnDefinition"]
  • Use this info to call RoGetParameterizedTypeInstanceIID. 
  • As RoGetParameterizedTypeInstanceIID does its thing, it'll pump us for information - by you guessed it! calling our "Locate" function.
    • we are provided with:
      • a name of a typedef
      • and a pointer to an IRoSimpleMetaDataBuilder interface. (I've written an interface library for this one).
    • We need to dig in the metadata for that info, then call the correct method of the builder to send it on.
  • A call to RoGetMetaDataFile will get us an pointer to a IMetadataImport2 interface. This is what we should use to find stuff. 

In our scenario, the first time "Locate" is called we're asked to resolve  "Windows.Foundation.Collections.IVector`1" So we need to determine what this is.. (a paramatised interface).  Therefore we should call builder.SetParameterizedInterface(PIID, NumParams).

Next time around we get "Microsoft.UI.Xaml.Controls.RowDefinition". This is a class. So this time we should call builder.SetRuntimeClassSimpleDefault(ClassName, DefaultInterface, DefaultInterfaceIID).

DefaultInterfaceIID is optional though can be left null. In this case Locate() would be called a third time asking us to resolve the default interface (Microsoft.UI.Xaml.Controls.IRowDefinition). Because this is a non-paramatised interface, we'd then call builder.SetWinRtInterface(IID).

  •     Once RoGetParameterizedTypeInstanceIID is satisfied it'll spit out what it thinks the IID is. If we call the wrong method on IRoSimpleMetaDataBuilder or provide bad info, then unsurprisingly we get an incorrect IID as a result!

I'll do a bit of a tidy up, and pop some code up in a day or two :)

Posted

And the example as promised...

Bear in mind the locator could be asked to find things such as structs and enums depending on what we want to resolve. For this example though, I'm only covering the three types I mentioned above. That is:

  • Parameterised Interfaces
  • Classes
  • Interfaces

IIDResolveExample.zip

Posted

Hi all -

Picking up where we left off.. After working on the generator a bit, out row collection now looks like this.

(262,0) Supported Interfaces:
Class: Microsoft.UI.Xaml.Controls.RowDefinitionCollection
{EAA65D85-3FA0-5BC2-B1BE-7BC722253EAE} - IVectorView_1_RowDefinition_
{5DDD9577-3F94-567F-BEEF-540568522289} - IVector_1_RowDefinition_
{B0B30930-7697-561E-BD6E-FBBB1AD17C50} - IIterable_1_RowDefinition_
{E7BEAEE7-160E-50F7-8789-D63463F979FA} - IDependencyObject
{EB24C20B-9816-4AC7-8CFF-36F67A118F4E}
{00000038-0000-0000-C000-000000000046} - IWeakReferenceSource
{DF0B3D60-548F-101B-8E65-08002B2BD119} - ISupportErrorInfo
{00000000-0000-0000-C000-000000000046} - IUnknown
{AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90} - IInspectable

And the updated object explorer shows us this:

TypeDef : Microsoft.UI.Xaml.Controls.RowDefinitionCollection
                  Extends : System.Object
      (Default) Interface : Windows.Foundation.Collections.IVector`1<Microsoft.UI.Xaml.Controls.RowDefinition>
                Interface : Windows.Foundation.Collections.IIterable`1<Microsoft.UI.Xaml.Controls.RowDefinition>

Why is IVectorView at the top of $pRowDefs, where the default interface usually lives? This is a mystery for now - I can't see it anywhere obvious in the metadata...  By inspecting the object pointer we can see that IVector is indeed where we land when we "get" the property, so the "default" attribute is presumably being respected.

Not finding IVectorView in the metadata is annoying, because we can resolve the IID from the type name, but not the other way around. So we'll probably still be in the dark for some of these GUIDs until I can dig out where that data lives. For an immediate (but horrible) fix to the generator, I'm just assuming anything employing IVector will also have an IVectorView interface.

Anyway continuing on... XAML layouts are usually described something like this, so I'm going to try to unpick that.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Button x:Name="btn1" Grid.Column="1" Grid.Row="1" Content="Click Me" Click="ClickFunc" />
</Grid>

And now we know where IVector is, I guess we can do something like this:

Local $pColDefs = IGrid_GetColumnDefinitions($pGrid)
Local $pRowDefs = IGrid_GetRowDefinitions($pGrid)

Local $tGridLen = DllStructCreate("align 4;double Value;long GridUnitType")
$tGridLen.Value = 1
$tGridLen.GridUnitType = $mGridUnitType["Star"]

Local $pCol0 = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Controls.ColumnDefinition")
Local $pCol1 = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Controls.ColumnDefinition")
Local $pRow0 = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Controls.RowDefinition")
Local $pRow1 = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Controls.RowDefinition")

IVector_Append($pColDefs, $pCol0)
IVector_Append($pColDefs, $pCol1)
IVector_Append($pRowDefs, $pRow0)
IVector_Append($pRowDefs, $pRow1)

IColumnDefinition_SetWidth($pCol0, $tGridLen)
IColumnDefinition_SetWidth($pCol1, $tGridLen)
IRowDefinition_SetHeight($pRow0, $tGridLen)
IRowDefinition_SetHeight($pRow1, $tGridLen)

And looking through the explorer, I see we can set a border. So if we do something like this we might see something!

Local $pBrushFact =  _WinRT_GetActivationFactory("Microsoft.UI.Xaml.Media.SolidColorBrush", $sIID_ISolidColorBrushFactory)
Local $tColor = DllStructCreate("byte A; byte R; byte G; byte B")
$tColor.A = 0xFF
$tColor.R = 0xFF
$tColor.G = 0
$tColor.B = 0
    
Local $pBrush = ISolidColorBrushFactory_CreateInstanceWithColor($pBrushFact, $tColor)
$tColor = ISolidColorBrush_GetColor($pBrush)
IGrid_SetBorderBrush($pGrid, $pBrush)

Local $tThickness = DllStructCreate("double Left; double Top; double Right; double Bottom")
$tThickness.Left = 2
$tThickness.Top = 2
$tThickness.Right = 2
$tThickness.Bottom = 2
IGrid_SetBorderThickness($pGrid, $tThickness)

And finally, sort out the island bit as we did in our original button example:

IDesktopWindowXamlSource_SetContent($pDesktopWinXamlSrc, $pGrid)
Local $pBridge = IDesktopWindowXamlSource_GetSiteBridge($pDesktopWinXamlSrc)
_WinRT_SwitchInterface($pBridge, $sIID_IDesktopSiteBridge)

;Resize/move the container in the window (optional)!
$tRect.X = 20
$tRect.Y = 20
$tRect.Width = 340
$tRect.Height = 120
IDesktopSiteBridge_MoveAndResize($pBridge, $tRect)

IDispatcherQueue3_RunEventLoop($pDispatchQ)

And Ok, so that's a border of sorts - but its also not red...

image.png.3b65858194a77ebe53afbaac157f7108.png

In ISolidColorBrushFactory_CreateInstanceWithColor, tColor as passed as "struct*" - but it should be passed "struct". So we've found another bug somewhere in the generation script :( . (we're sending a ptr as a the literal ARGB value!)

Not sure if we should actually be seeing a grid at this point or we're just seeing the outer edge of the grid. Regardless, next bit of reading is all about getting a control in a cell.  (provided we've defined our rows and column correctly!) This looks to be done via an "Attached Property".  Best guess is we associate the button with the grid via the Grid's IPanel interface, then we assign it a grid spot via  IDependancyObject::SetValue.

That's all for now!

PS. Let me know if these long blog-like posts are getting annoying, and I'll happily dial it down a bit!

Posted (edited)
27 minutes ago, MattyD said:

PS. Let me know if these long blog-like posts are getting annoying, and I'll happily dial it down a bit!

I personally really appreciate these longer blog-like posts. You are learning this WinUI3 stuff as you go and we are learning along with you (thanks to you). I think that this teaching style of posts is the only way to go, honestly.

I also think that this has a lot of great potential for any of us who use AutoIt for making GUI programs. This has the potential to create more opportunities for designing modern looking GUI apps in AutoIt and that is definitely a bonus.

Therefore, for any of us that have an interest in that area and who are following along with you, I personally am very thankful for the amount of detail that you share in your posts. You have a good teaching style.

Edited by WildByDesign
spelling
Posted (edited)

Okeydoke no problems - thanks for the compliment :) 

So Before we get to back to grid, here's a bonus post on the button code as I didn't really explain this earlier.

Local $pInner
Local $pButton_Fact = _WinRT_GetActivationFactory("Microsoft.UI.Xaml.Controls.Button", $sIID_IButtonFactory)
Local $pButton = IButtonFactory_CreateInstance($pButton_Fact, 0, $pInner)

Firstly a quick explanation on parameters for the factory (0, and $pInner) - these are the  "baseInterface" and "innerInterface" respectively... The factory for all controls look like this because they are "Composable objects", and on the subject Microsoft tells us:

Runtime classes optionally support composition—the ability for multiple class instances to be combined into what appears to be a single object from the outside. WinRT uses composition as a form of runtime class inheritance.

so there you go - composition is a technique to smoosh two classes together. If we keep going through the article we find:

... take the example that Button composes Control, which in turn composes UIElement....

... Building on the earlier Button -> Control -> UIElement example, the button class would be activating by calling one of its composition factory methods and passing null for the outer parameter. Button would in turn activate a Control instance, passing a reference to itself as the outer parameter....

So from all that, I'm reading we shouldn't specify the "baseinterface" for a control.  I assume there could technically be other controls based on a "button" - these would internally pass themselves as a button's baseInterface on creation (if they actually exist!). 

The "InnerIntnerface" param on the other hand is outputted from the factory - so this is why we define the $pInner variable.  I'm still not 100% across all this to be fair, but its probably all we need to know at this point in time...

Anyway, with the factory out of the way, we'll get on to setting the button text. Some example XAML looks like this:

<Button x:Name="btn1" Content="Push Me" Click="OnClick"/>

And the first few interfaces of the control look like this:

Class: Microsoft.UI.Xaml.Controls.Button
{216C183D-D07A-5AA5-B8A4-0300A2683E87} - IButton
{65714269-2473-5327-A652-0EA6BCE7F403} - IButtonBase
{07E81761-11B2-52AE-8F8B-4D53D2B5900A} - IContentControl

IContentControl looks like the right place for the "Content" property, so lets inspect that. - We can see the following methods look promising.

Method : get_Content
 Fn : value = get_Content()
 P0 : value                type: System.Object 
 Method : put_Content
 Fn : put_Content(In value)
 P0 : value                type: void 
 P1 : value                type: System.Object

But System.Object could be anything... so how do we know what object to provide? Shall we do a IContentControl_GetContent and see what object lives there? Its a good idea in theory, but we just get a null ptr because we haven't set anything yet!. Unfortunately the Microsoft IContentControl documentation only gives you a bunch of XML examples, so that's no use either.

So after getting nowhere on the internet, I knew "Windows.Foundation" was probably a good place to have a dig around.  I basically just thumbed through the explorer until I found something that looked right. The PropertyValue object looks very close to a variant struct - and that seemed like a good fit for a generic sounding thing called IContentControl::put_Content.  Give that a go and...

_WinRT_SwitchInterface($pButton, $sIID_IContentControl)
Local $pProp_Fact = _WinRT_GetActivationFactory("Windows.Foundation.PropertyValue", $sIID_IPropertyValueStatics)
Local $pProp = IPropertyValueStatics_CreateString($pProp_Fact, "Push Me")
IContentControl_SetContent($pButton, $pProp)

yep, like magic it worked.

Creating the event handler was actually a much easier task. We've created delegates before with other controls so that process was simple enough by now.
If anyone needs to catch up - the underlying principal to creating these are probably best  explained here.  The only difference from that example is that the WinRT delegates are a bit more constrained. They'll take 2 parameters (apart from itself), a pointer to the "sender" object, and one called "arguments".

;Setup the delegate for handling button pushes.
Global $__hQueryInterface = DllCallbackRegister("__QueryInterface", "long", "ptr;ptr;ptr")
Global $__hAddRef = DllCallbackRegister("__AddRef", "long", "ptr")
Global $__hRelease = DllCallbackRegister("__Release", "long", "ptr")

Global $tBtnClick_VTab = DllStructCreate("ptr pFunc[4]")
$tBtnClick_VTab.pFunc(1) = DllCallbackGetPtr($__hQueryInterface)
$tBtnClick_VTab.pFunc(2) = DllCallbackGetPtr($__hAddRef)
$tBtnClick_VTab.pFunc(3) = DllCallbackGetPtr($__hRelease)
Global $hBtnClick = DllCallbackRegister("BtnClick", "none", "ptr;ptr;ptr")
$tBtnClick_VTab.pFunc(4) = DllCallbackGetPtr($hBtnClick)

Global $tBtnClick = DllStructCreate("ptr pVTab;int iRefCnt")
$tBtnClick.pVTab = DllStructGetPtr($tBtnClick_VTab)
$tBtnClick.iRefCnt = 1
Global $pBtnClick = DllStructGetPtr($tBtnClick)

Func BtnClick($pThis, $pSender, $pArgs)
    #forceref $pThis, $pSender, $pArgs
    Local $pSrc = IRoutedEventArgs_GetOriginalSource($pArgs) ; $pSrc is the same as $pSender.
    MsgBox(0, "TestWin", "You are pushing my buttons.")
EndFunc

Func __QueryInterface($pThis, $pIID, $ppObj)
    Local $hResult = $S_OK
    If Not $ppObj Then
        $hResult =  $E_POINTER
    ElseIf _WinAPI_StringFromGUID($pIID) = $sIID_IUnknown Then
        DllStructSetData(DllStructCreate("ptr", $ppObj), 1, $pThis)
        __AddRef($pThis)
    Else
        $hResult = $E_NOINTERFACE
    EndIf
    Return $hResult
EndFunc

Func __AddRef($pThis)
    Local $tThis = DllStructCreate("ptr pVTab;int iRefCnt", $pThis)
    $tThis.iRefCnt += 1
    Return $tThis.iRefCnt
EndFunc

Func __Release($pThis)
    Local $tThis = DllStructCreate("ptr pVTab;int iRefCnt", $pThis)
    $tThis.iRefCnt -= 1
    Return $tThis.iRefCnt
EndFunc

Looking though IButtonBase, it is very clear how to register our delegate. For reference, All add_* and remove_* functions are related to registering delegates!

Method : add_Click
   Fn : token = add_Click(In handler)
   P0 : token                type: Windows.Foundation.EventRegistrationToken 
   P1 : handler              type: Microsoft.UI.Xaml.RoutedEventHandler 
  Method : remove_Click
   Fn : remove_Click(In token)
   P0 : token                type: void 
   P1 : token                type: Windows.Foundation.EventRegistrationToken

And that translates to:

_WinRT_SwitchInterface($pButton, $sIID_IButtonBase)
IButtonBase_AddHdlrClick($pButton, $pBtnClick)

Finally, by inspecting RoutedEventHandler we can see that the "arguments" param will be an Microsoft.UI.Xaml.RoutedEventArgs object which looks like this.

TypeDef : Microsoft.UI.Xaml.RoutedEventArgs
                  Extends : System.Object
               Composable : Microsoft.UI.Xaml.IRoutedEventArgsFactory
      (Default) Interface : Microsoft.UI.Xaml.IRoutedEventArgs
                 Property : OriginalSource
                   Method : .ctor
                       Fn : .ctor()
                       P0 :                      type: void 
                   Method : get_OriginalSource
                       Fn : value = get_OriginalSource()
                       P0 : value                type: System.Object

 

Edited by MattyD
Clarity
Posted

Alrighty, here's a couple of general notes around this attachment, then there will be a follow up post with more detail around the Grid stuff. (last one for the weekend I promise!)

  • We can now include "Include\WinRT_WinUI3.au3" which exposes _WinUI3_Startup and _WinUI3_Shutdown. This is basically those boostrapper calls to spin up and down the runtime.
  • In WinRT.au3, I've popped _WinRT_SwitchInterface in as a standard func so we don't have to do multi-step calls through IUnknown to jump between interfaces.
  • I've also added a _WinRT_CreateDelegate() and _WinRT_DestroyDelegate() in WinRT.au3 to do the heavy lifting for creating delegates. So the syntax now looks like this:
; Creation
$pBtnClickDelegate = _WinRT_CreateDelegate("BtnClick")
$iBtnClickHandlerTkn = IButtonBase_AddHdlrClick($pButton, $pBtnClickDelegate)

; Teardown
IButtonBase_RemoveHdlrClick($pButton, $iBtnClickHandlerTkn)
_WinRT_DestroyDelegate($pBtnClickDelegate)

Func BtnClick($pThis, $pSender, $pArgs)
    ;Do stuff   
EndFunc

I'll still need to fix up a few more things before popping this all up on sourceforge - but just thought I'd get these quality of life things out the door while I'm feeling energetic!  

WindowTest - Grid.zip

Posted (edited)

Last time we left this, we were looking at how to attach a button to a grid spot:

The first task is to associate the button with the grid - which is done via the grid's IPanel::GetChildren. Calling that gives us a Microsoft.UI.Xaml.Controls.UIElementCollection, which is navigated exactly the same way as the earlier collections we looked at. We can just "Append" the button. Interestingly enough, there's no need to switch over to the button's IUIElement interface before adding it to the collection.

_WinRT_SwitchInterface($pGrid, $sIID_IPanel)
Local $pGridChildren = IPanel_GetChildren($pGrid)
IVector_Append($pGridChildren, $pButton)

Just adding the button as child of the grid caused it to appear in row 0 ,column 0 - which was a pleasant surprise!

image.png.146afedb4b561747f1274a1f75dac809.png

So now we need to try and move it. Remember that example XAML? We need to access those attached "Grid.Column" and "Grid.Row" properties on the button. The documentation on attached properties essentially tells us how this should be done. 

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Button x:Name="btn1" Grid.Column="1" Grid.Row="1" Content="Click Me" Click="ClickFunc" />
</Grid>

The example in the documentation is for something slightly different, but the concept is the same: (they are attaching the canvas.TopProperty to a checkbox, we're attaching "Grid.Row" to a button)

 Canvas myC = new Canvas();
 CheckBox myCheckBox = new CheckBox();
 myCheckBox.Content = "Hello";
 myC.Children.Add(myCheckBox);
 myCheckBox.SetValue(Canvas.TopProperty,75);

Going line by line through that, It looks like we've done everything but the last line for our implementation. And to progress we'll first need to get a representation of the property. In the explorer, we can find a "get_RowProperty" on the IGridStatics interface. 

TypeDef : Microsoft.UI.Xaml.Controls.IGridStatics
        GUID : {EF9CF81D-A431-50F4-ABF5-3023FE447704}
Exclusive To : Microsoft.UI.Xaml.Controls.Grid
etc...
------------------------------------------------------------
      Method : get_RowProperty
          Fn : value = get_RowProperty()
          P0 : value                type: Microsoft.UI.Xaml.DependencyProperty

Statics interfaces live on factories, so we need to jump on $pGrid_Fact to get our properties. 

_WinRT_SwitchInterface($pGrid_Fact, $sIID_IGridStatics)
Local Static $pGridRowProp = IGridStatics_GetRowProperty($pGrid_Fact)
Local Static $pGridColProp = IGridStatics_GetColumnProperty($pGrid_Fact)

Next, we see on the child object (the button) that there is a SetValue method on IDependencyObject

TypeDef : Microsoft.UI.Xaml.IDependencyObject
         GUID : {E7BEAEE7-160E-50F7-8789-D63463F979FA}
 Exclusive To : Microsoft.UI.Xaml.DependencyObject
     Property : Dispatcher
     Property : DispatcherQueue
       Method : GetValue
           Fn : result = GetValue(In dp)
           P0 : result               type: System.Object 
           P1 : dp                   type: Microsoft.UI.Xaml.DependencyProperty 
       Method : SetValue
           Fn : SetValue(In dp, In value)
           P0 : result               type: void 
           P1 : dp                   type: Microsoft.UI.Xaml.DependencyProperty 
           P2 : value                type: System.Object

We need an Microsoft.UI.Xaml.DependencyProperty, and we have a Microsoft.UI.Xaml.DependencyProperty.  So happy days.

Now we need a "value" param for setValue - and that's of type System.Object 😮. We should have a good idea whats been asked of us though based on our earlier adventure with setting button text.. But unlike before, we can just call "getValue" to see what we need.

_WinRT_SwitchInterface($pButton, $sIID_IDependencyObject)
Local $pValue = IDependencyObject_GetValue($pButton, $pRowProp)
_WinRT_DisplayInterfaces($pValue)

And huh?

(291,0) Supported Interfaces:
Class: Windows.Foundation.IReference`1<Int32>
{4BD682DD-7554-40E9-9A9B-82654EDE7E62} - IPropertyValue
{00000038-0000-0000-C000-000000000046} - IWeakReferenceSource
{548CEFBD-BC8A-5FA0-8DF2-957440FC8BF4}

we get IReference`1<Int32> -  I was expecting Windows.Foundation.PropertyValue... We can see we have an IPropertyValue interface - so maybe this is just what an instantiated propertyValue looks like when wrapping an integer...

Local $pRowAddress = IPropertyValueStatics_CreateInt32($pProp_Fact, 1)
_WinRT_DisplayInterfaces($pRowAddress)

And yes, the above code returns the same thing - So continuing on!

Local $pRowAddress = IPropertyValueStatics_CreateInt32($pProp_Fact, 1)
Local $pColAddress = IPropertyValueStatics_CreateInt32($pProp_Fact, 1)
IDependencyObject_SetValue($pButton, $pRowProp, $pRowAddress)
IDependencyObject_SetValue($pButton, $pColProp, $pColAddress)

Then we can finally see our handiwork!

image.png.e6decee58fdee1f55f463888427a72a5.png

Edited by MattyD
Posted

Ok. Here to test and report :) 

!>14:51:01 AutoIt3 ended. rc:-1073741819    ( running "WindowTest Grid.au3" )

I added a bunch of 

ConsoleWrite('@@(' & @ScriptLineNumber & ') : ..passed this line...' & @CRLF)

..just to see how far it got and it crashes at: IButtonBase_AddHdlrClick($pButton, $pBtnClick)

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted (edited)

OK thanks!

I've managed to reproduce that on a machine that didn't have the runtime installed - does installing that that fix things?

I threw together _WinU3I_Startup() just before uploading, and obviously didn't reintroduce error checking there in the example... And it probably wouldn't have helped anyway, because I've made a mistake in the function!

That last line of the func should be: (Line 37 in  WinRT_WinUI3.au3)

Return SetError($aCall[0], 0, $aCall[0] = $S_OK)

So _WinUI3_Startup() should return False on failure, and spit out an error code.

Edited by MattyD
Posted (edited)
1 hour ago, MattyD said:

I've managed to reproduce that on a machine that didn't have the runtime installed - does installing that fix things?

Yes it did.

Fun demo, moving the button around :) 

;That last line of the func should be: (Line 37 in  WinRT_WinUI3.au3)
;Return SetError($aCall[0], 0, $aCall[0] = $S_OK)
;So _WinUI3_Startup() should return False on failure, and spit out an error code.

... ...
_WinRT_Startup()
_WinUI3_Startup()
If @error Then
    ConsoleWrite('! install "https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/downloads" to run this.' & @CRLF)
    Exit 4
EndIf
... ...

 

Edited by argumentum

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Posted (edited)

To flesh this out a bit more - I attempted to recreate this example..

The XML looks like this...

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="3*"/>
        <ColumnDefinition Width="5*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Border Background="#2f5cb6"/>
    <Border Grid.Column ="1" Background="#1f3d7a"/>
    <Border Grid.Row="1" Grid.ColumnSpan="2" Background="#152951"/>

<StackPanel Grid.Column="1" Margin="40,0,0,0" VerticalAlignment="Center">
    <TextBlock Foreground="White" FontSize="25" Text="Today - 64° F"/>
    <TextBlock Foreground="White" FontSize="25" Text="Partially Cloudy"/>
    <TextBlock Foreground="White" FontSize="25" Text="Precipitation: 25%"/>
</StackPanel>

<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal"
            HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBlock Foreground="White" FontSize="25" Text="High: 66°" Margin="0,0,20,0"/>
    <TextBlock Foreground="White" FontSize="25" Text="Low: 43°" Margin="0,0,20,0"/>
    <TextBlock Foreground="White" FontSize="25" Text="Feels like: 63°"/>
</StackPanel>

<Image Margin="20" Source="Assets/partially-cloudy.png"/>
</Grid>

Now last time out we were specifying our Row/Column definitions like this..

Local $tGridLen = DllStructCreate("align 1;double Value;ulong GridUnitType")
$tGridLen.GridUnitType = $mGridUnitType["Star"]

$pRowDef = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Controls.RowDefinition")
$tGridLen.Value = 2
IRowDefinition_SetHeight($pRowDef, $tGridLen)
IVector_Append($pRowDefs, $pRowDef)

The default interface for a "RowDefinition" is IRowDefinition - but it seems ActivateInstance doesn't put us there. So appending the object to the collection worked OK- the cells obviously appeared. But setting the height property was actually failing. 

So the learning is: If you're instantiating via ActivateInstance, you should always follow up with a  _WinRT_SwitchInterface.  

Local $tGridLen = DllStructCreate("align 1;double Value;ulong GridUnitType")
$tGridLen.GridUnitType = $mGridUnitType["Star"]

$pRowDef = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Controls.RowDefinition")
_WinRT_SwitchInterface($pRowDef, $sIID_IRowDefinition) ;ADD THIS!
$tGridLen.Value = 2
IRowDefinition_SetHeight($pRowDef, $tGridLen)
IVector_Append($pRowDefs, $pRowDef)

In  this example, we have a few new controls:

stackpanels - these work like grids, but the're 1-dimentional. (Edit: Well they have a width and height, but its just one row or one column of panels!)

You don't need to assign a "grid spot" for the controls of a stackpanel though - they seem to appear in the order they were added in the IPanel::Children collection.  Vectors do assign indexes to child items by memory - so I'd imagine if we shuffled items around via IVector, the GUI would probably follow suit (untested).  

textblocks - not much to be said about these. But at this point, I'll quickly mention that IFrameworkElement interface seems to look after most of the spatial things for a control (its margins, vertical and horizontal alignment, min/max sizing etc).  And this is common across everything we've seen so far. 

Then we have an image - this one is a bit more interesting.  You load it in with a URI, so for local storage we'll need to specify something like file:///C:/path/to/file.blah. 

Once you have yourself an uri object - we can bring the file in via a BitmapImage object. And finally we associate that with the UI object "controls.Image"

Local $pURI_Fact = _WinRT_GetActivationFactory("Windows.Foundation.Uri", $sIID_IUriRuntimeClassFactory)
Local $pImageURI = IUriRuntimeClassFactory_CreateWithRelativeUri($pURI_Fact, "file:///C:/FilePath/", "image.png")

Local $pBitmap = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Media.Imaging.BitmapImage")
_WinRT_SwitchInterface($pBitmap, $sIID_IBitmapImage)
IBitmapImage_SetUriSource($pBitmap, $pImageURI)

Local $pImage = _WinRT_ActivateInstance("Microsoft.UI.Xaml.Controls.Image")
_WinRT_SwitchInterface($pImage, $sIID_IImage)
IImage_SetSource($pImage, $pBitmap)

The loading is asynchronous, so if you need the dimensions of the source for example - those details are only available once the image is ready.  If those properties are important we can register a delegate that will fire once the image is loded. Also, if the image fails to load, there's a delegate for that too...

Local $pImgFailedDgte = _WinRT_CreateDelegate("ImageLoadFail")
Local $iImgFailedDgteTkn = IBitmapImage_AddHdlrImageFailed($pBitmap, $pImgFailedDgte)

;Attempt to load image
IBitmapImage_SetUriSource($pBitmap, $pImageURI)

Func ImageLoadFail($pThis, $pSource, $pArgs)
    #forceref $pThis, $pSource, $pArgs
    ConsoleWrite("Image failed to load!" & @CRLF)
EndFunc

 Final point on this example, I've tied the content bridge's size  (the container encompassing the xaml controls) to the Windows WM_SIZE message.

Case $WM_SIZE
    $tRect.Width = BitAND(0xFFFF, $lParam)
    $tRect.Height = BitShift($lParam, 16)
    IDesktopSiteBridge_MoveAndResize($pBridge, $tRect)
    $iReturn = _WinAPI_DefWindowProcW($hWnd, $iMsg, $wParam, $lParam)

And resizing the window actually works remarkably well. I guess I was expecting some flicker or something- so that was a pleasant surprise! 

image.png.88d613ccca08a111ae112f1407faddba.png

 

WindowTest Grid.zip

Edited by MattyD

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...