Popular Post MattyD Posted July 8 Popular Post Posted July 8 (edited) Hi all - This is an offshoot of the WinRT Project. I'm posting my progress here while I'm bumbling around with WinUI3 - just so the main project doesn't get too cluttered. If I get things working I'll merge the two projects back together. That's the plan anyway! Theres not much there at the moment, but if you wish to try it out you'll need to: download and extract the example (The attachment it too big for here, so I've popped it in sourceforge) Go here and download the latest Redistributable package of the Windows App SDK. Currently this is 1.7.3 (look in the table for the link.). Once you've extracted that, go to the MSIX > win10-x64 folder and install Microsoft.WindowsAppRuntime.1.7.msix It seems the x86 libraries don't work at the moment. (thanks for testing Gianni) Progress so far... Cheers, Matt Edited July 28 by MattyD Gianni, WildByDesign, argumentum and 2 others 5
Gianni Posted July 9 Posted July 9 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! 👍 MattyD 1 Chimp small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....
MattyD Posted July 10 Author Posted July 10 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. Gianni 1
MattyD Posted July 21 Author Posted July 21 (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: https://www.nuget.org/packages/Microsoft.WindowsAppSDK/ Download (on the right) .nupkg file. extract contents... look in: .\microsoft.windowsappsdk.1.7.250606001\runtimes\win-x64\native\ Microsoft.WindowsAppRuntime.Bootstrap.dll Edited July 21 by MattyD
MattyD Posted July 26 Author Posted July 26 (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 July 26 by MattyD
MattyD Posted July 28 Author Posted July 28 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
WildByDesign Posted July 28 Posted July 28 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. MattyD 1
MattyD Posted July 28 Author Posted July 28 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! WildByDesign 1
MattyD Posted August 14 Author Posted August 14 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!!! Gianni, Danyfirex and WildByDesign 3
MattyD Posted August 20 Author Posted August 20 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 Gianni and CYCho 2
MattyD Posted August 22 Author Posted August 22 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 WildByDesign 1
MattyD Posted Friday at 01:32 PM Author Posted Friday at 01:32 PM 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... 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! WildByDesign and ioa747 2
WildByDesign Posted Friday at 01:53 PM Posted Friday at 01:53 PM (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 Friday at 01:59 PM by WildByDesign spelling ioa747, MattyD and Gianni 3
MattyD Posted Saturday at 01:10 PM Author Posted Saturday at 01:10 PM (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". expandcollapse popup;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 Saturday at 01:36 PM by MattyD Clarity WildByDesign 1
MattyD Posted Sunday at 07:29 AM Author Posted Sunday at 07:29 AM 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 WildByDesign and Gianni 2
MattyD Posted Sunday at 09:13 AM Author Posted Sunday at 09:13 AM (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! 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! Edited Sunday at 09:30 AM by MattyD WildByDesign 1
argumentum Posted Sunday at 06:56 PM Posted Sunday at 06:56 PM 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.
MattyD Posted Monday at 12:41 AM Author Posted Monday at 12:41 AM (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 Monday at 12:44 AM by MattyD argumentum 1
argumentum Posted Monday at 01:32 AM Posted Monday at 01:32 AM (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 Monday at 01:42 AM by argumentum MattyD 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting.
MattyD Posted Monday at 01:35 PM Author Posted Monday at 01:35 PM (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! WindowTest Grid.zip Edited Monday at 01:49 PM by MattyD argumentum, Gianni and WildByDesign 2 1
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now