# pymsbuild-winui
This is a highly experimental and early stage extension to
[pymsbuild](https://pypi.org/project/pymsbuild). It uses the Microsoft
Windows App SDK to build GUI apps based on XAML.
The sample projects in [testdata/app1](https://github.com/zooba/pymsbuild-winui/tree/master/tests/testdata/app1)
and [testdata/BasicPhotoViewer](https://github.com/zooba/pymsbuild-winui/tree/master/tests/testdata/BasicPhotoViewer)
are currently the best starting point.
Good luck!
(Expressions of interest are welcome right now, but not contributions.)
# Quick Start
In your `_msbuild.py`, import `WinUIExe`, `XamlApp` and `XamlPage` from
`pymsbuild_winui` (using `import *` is okay).
The `WinUIExe` element will take the `XamlApp` and one or more
`XamlPage`s and generate an executable file along with _a lot_ of
support files. The package this element goes into should contain the
Python sources and any other content, but only XAML items should be in
the `WinUIExe` itself.
The name of the `WinUIExe` must match the namespaces used in the XAML
files `x:Class` attributes, and must also be an importable Python
module that will contain the implementation of each page.
```python
from pymsbuild import *
from pymsbuild_winui import *
METADATA = {...}
PACKAGE = Package(
'PhotoViewerApp',
PyFile("PhotoViewer.py"),
PyFile("image_info.py"),
WinUIExe(
"PhotoViewer",
XamlApp("app.xaml"),
XamlPage("MainWindow.xaml"),
IncludePythonRuntime=True, # these both default to True, they
IncludeAppRuntime=True, # are just here for the example
),
)
def init_PACKAGE(tag):
# WinUIExe needs to be passed the wheel tag to complete setup
PACKAGE.find("PhotoViewer").init_PACKAGE(tag)
```
In the main Python module (`PhotoViewer.py` in the above example), each
XAML page must have a class with matching name and the initializer
shown below.
```python
class MainWindow:
def __init__(self, view, viewmodels):
...
```
The `view` argument is a reference to the XAML instance. You should
keep this around as `self.view` to use it later.
The `viewmodels` argument has each defined viewmodel as an attribute.
You should now update the `view.models` dict to map your model types to
their viewmodels. This will enable `view.wrap()` and `view.unwrap()` to
work.
```python
self.view.models[image_info.ImageInfo] = viewmodels.ImageInfo
self.view.models[image_info.ImagesRepository] = viewmodels.ImageRepository
```
The `view` object also contains any properties that were defined in the
XAML. Properties and viewmodels use a private XML namespace that is
processed at build time.
```xml
...
xmlns:py="http://schemas.stevedower.id.au/pymsbuild/winui"
...
<py:ViewModel Name="ImageInfo">
<py:Property Name="Name" Type="str" />
<py:Property Name="Path" Type="str" />
</py:ViewModel>
<py:ViewModel Name="ImageRepository">
<py:Property Name="Images" Type="list[PhotoViewer.ImageInfo]" />
</py:ViewModel>
<py:Property Name="ImagesRepository" Type="PhotoViewer.ImageRepository" />
```
Property types are _very_ limited, try to stick to primitives or
other viewmodels. Notice that the viewmodel type must include the
namespace, which is implicitly added to the name specified when
defining it.
The `list[]` property type defines a readonly property containing an
observable list that can contain any object type. The type specifier is
for self-documentation only, it is never verified. You cannot assign to
list properties, but can use their `append`, `clear`, `extend`,
`insert` and `replace_all` methods to modify the contents. Note that
each update to an observable list may trigger UI updates if it has been
bound, and so `replace_all` is recommended for batching updates
together.
```python
# Python list of Python objects
images = [ImageInfo(p.name, p) for p in paths]
# Update the viewmodel with wrapped instances
viewmodel.Images.replace_all(view.wrap(i) for i in images)
```
Event handlers for common types will be automatically detected from
your XAML. However, it is also possible to explicitly define them in
case of needing to override the sender or argument type.
```xml
<!-- Not required for these particular events, but just syntax examples! -->
<py:EventHandler Name="ImageClick" Sender="object" />
<py:EventHandler Name="OnElementPointerEntered" EventArgs="Microsoft.UI.Xaml.Input.PointerRoutedEventArgs" />
```
Arguments are passed through with exactly the type they are defined as,
which mean mean `sender` will need to be down-cast in order to access
its members. The `.as_()` function takes a fully qualified name as a
string, and will raise if the conversion fails. The `e` argument will
already be usable as the type specified in the `EventArgs` attribute.
Event handlers found automatically will pass in the type they were
defined on.
```python
def ImageClick(self, sender, e):
sender = sender.as_("Microsoft.UI.Xaml.Controls.Button")
...
def OnElementPointerEntered(self, sender, e):
...
```
Finally, the `view` object contains any controls that were named with
`x:Name` in the XAML. In general, it is best to avoid working with
controls directly, and instead try to use XAML bindings (`{x:bind}`) to
properties and viewmodels. All properties and viewmodels provide update
notifications, and so `Text="{x:bind Message,Mode=OneWay}"` is more efficient
than writing `self.view.StatusControl.Text = Message`.
Raw data
{
"_id": null,
"home_page": "https://github.com/zooba/pymsbuild-winui",
"name": "pymsbuild-winui",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "your,keywords,go,here",
"author": "Steve Dower",
"author_email": "steve.dower@python.org",
"download_url": "https://files.pythonhosted.org/packages/4c/33/6e57acd7865ae8ff29d845fe6dc85de3a7a323eef1f4ecc65b130f5ef655/pymsbuild_winui-0.0.1a8.tar.gz",
"platform": null,
"description": "# pymsbuild-winui\n\nThis is a highly experimental and early stage extension to\n[pymsbuild](https://pypi.org/project/pymsbuild). It uses the Microsoft\nWindows App SDK to build GUI apps based on XAML.\n\nThe sample projects in [testdata/app1](https://github.com/zooba/pymsbuild-winui/tree/master/tests/testdata/app1)\nand [testdata/BasicPhotoViewer](https://github.com/zooba/pymsbuild-winui/tree/master/tests/testdata/BasicPhotoViewer)\nare currently the best starting point.\n\nGood luck!\n\n(Expressions of interest are welcome right now, but not contributions.)\n\n# Quick Start\n\nIn your `_msbuild.py`, import `WinUIExe`, `XamlApp` and `XamlPage` from\n`pymsbuild_winui` (using `import *` is okay).\n\nThe `WinUIExe` element will take the `XamlApp` and one or more\n`XamlPage`s and generate an executable file along with _a lot_ of\nsupport files. The package this element goes into should contain the\nPython sources and any other content, but only XAML items should be in\nthe `WinUIExe` itself.\n\nThe name of the `WinUIExe` must match the namespaces used in the XAML\nfiles `x:Class` attributes, and must also be an importable Python\nmodule that will contain the implementation of each page.\n\n```python\nfrom pymsbuild import *\nfrom pymsbuild_winui import *\n\nMETADATA = {...}\n\nPACKAGE = Package(\n 'PhotoViewerApp',\n PyFile(\"PhotoViewer.py\"),\n PyFile(\"image_info.py\"),\n WinUIExe(\n \"PhotoViewer\",\n XamlApp(\"app.xaml\"),\n XamlPage(\"MainWindow.xaml\"),\n IncludePythonRuntime=True, # these both default to True, they\n IncludeAppRuntime=True, # are just here for the example\n ),\n)\n\ndef init_PACKAGE(tag):\n # WinUIExe needs to be passed the wheel tag to complete setup\n PACKAGE.find(\"PhotoViewer\").init_PACKAGE(tag)\n```\n\nIn the main Python module (`PhotoViewer.py` in the above example), each\nXAML page must have a class with matching name and the initializer\nshown below.\n\n```python\nclass MainWindow:\n def __init__(self, view, viewmodels):\n ...\n```\n\nThe `view` argument is a reference to the XAML instance. You should\nkeep this around as `self.view` to use it later.\n\nThe `viewmodels` argument has each defined viewmodel as an attribute.\nYou should now update the `view.models` dict to map your model types to\ntheir viewmodels. This will enable `view.wrap()` and `view.unwrap()` to\nwork.\n\n```python\nself.view.models[image_info.ImageInfo] = viewmodels.ImageInfo\nself.view.models[image_info.ImagesRepository] = viewmodels.ImageRepository\n```\n\nThe `view` object also contains any properties that were defined in the\nXAML. Properties and viewmodels use a private XML namespace that is\nprocessed at build time.\n\n```xml\n...\nxmlns:py=\"http://schemas.stevedower.id.au/pymsbuild/winui\"\n...\n<py:ViewModel Name=\"ImageInfo\">\n <py:Property Name=\"Name\" Type=\"str\" />\n <py:Property Name=\"Path\" Type=\"str\" />\n</py:ViewModel>\n<py:ViewModel Name=\"ImageRepository\">\n <py:Property Name=\"Images\" Type=\"list[PhotoViewer.ImageInfo]\" />\n</py:ViewModel>\n<py:Property Name=\"ImagesRepository\" Type=\"PhotoViewer.ImageRepository\" />\n```\n\nProperty types are _very_ limited, try to stick to primitives or\nother viewmodels. Notice that the viewmodel type must include the\nnamespace, which is implicitly added to the name specified when\ndefining it.\n\nThe `list[]` property type defines a readonly property containing an\nobservable list that can contain any object type. The type specifier is\nfor self-documentation only, it is never verified. You cannot assign to\nlist properties, but can use their `append`, `clear`, `extend`,\n`insert` and `replace_all` methods to modify the contents. Note that\neach update to an observable list may trigger UI updates if it has been\nbound, and so `replace_all` is recommended for batching updates\ntogether.\n\n```python\n# Python list of Python objects\nimages = [ImageInfo(p.name, p) for p in paths]\n\n# Update the viewmodel with wrapped instances\nviewmodel.Images.replace_all(view.wrap(i) for i in images)\n```\n\nEvent handlers for common types will be automatically detected from\nyour XAML. However, it is also possible to explicitly define them in\ncase of needing to override the sender or argument type.\n\n```xml\n<!-- Not required for these particular events, but just syntax examples! -->\n<py:EventHandler Name=\"ImageClick\" Sender=\"object\" />\n<py:EventHandler Name=\"OnElementPointerEntered\" EventArgs=\"Microsoft.UI.Xaml.Input.PointerRoutedEventArgs\" />\n```\n\nArguments are passed through with exactly the type they are defined as,\nwhich mean mean `sender` will need to be down-cast in order to access\nits members. The `.as_()` function takes a fully qualified name as a\nstring, and will raise if the conversion fails. The `e` argument will\nalready be usable as the type specified in the `EventArgs` attribute.\nEvent handlers found automatically will pass in the type they were\ndefined on.\n\n```python\n def ImageClick(self, sender, e):\n sender = sender.as_(\"Microsoft.UI.Xaml.Controls.Button\")\n ...\n\n def OnElementPointerEntered(self, sender, e):\n ...\n```\n\nFinally, the `view` object contains any controls that were named with\n`x:Name` in the XAML. In general, it is best to avoid working with\ncontrols directly, and instead try to use XAML bindings (`{x:bind}`) to\nproperties and viewmodels. All properties and viewmodels provide update\nnotifications, and so `Text=\"{x:bind Message,Mode=OneWay}\"` is more efficient\nthan writing `self.view.StatusControl.Text = Message`.\n\n",
"bugtrack_url": null,
"license": "",
"summary": "A WinUI extension for PyMSBuild",
"version": "0.0.1a8",
"project_urls": {
"Bug Tracker": "https://github.com/zooba/pymsbuild-winui",
"Homepage": "https://github.com/zooba/pymsbuild-winui"
},
"split_keywords": [
"your",
"keywords",
"go",
"here"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f32acf111d21e6df3b16f625c050a904df2bfb13246355a387227cb283ce1b98",
"md5": "f50b8dafbc154d85bbc8aaf918986539",
"sha256": "21c62c41c4a30a1c4d40f1e879b571acbe233f4007c89398e81ae38d9390ca1b"
},
"downloads": -1,
"filename": "pymsbuild_winui-0.0.1a8-py3-none-win32.win_amd64.whl",
"has_sig": false,
"md5_digest": "f50b8dafbc154d85bbc8aaf918986539",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 71618,
"upload_time": "2023-11-02T17:34:29",
"upload_time_iso_8601": "2023-11-02T17:34:29.569344Z",
"url": "https://files.pythonhosted.org/packages/f3/2a/cf111d21e6df3b16f625c050a904df2bfb13246355a387227cb283ce1b98/pymsbuild_winui-0.0.1a8-py3-none-win32.win_amd64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "4c336e57acd7865ae8ff29d845fe6dc85de3a7a323eef1f4ecc65b130f5ef655",
"md5": "691f6ab75e68a70da08b7d443d1b5269",
"sha256": "97d7bc0c2f02e6a6f4a2297d8d747cc98d11b328e03a174e8c6379ac5fd0d244"
},
"downloads": -1,
"filename": "pymsbuild_winui-0.0.1a8.tar.gz",
"has_sig": false,
"md5_digest": "691f6ab75e68a70da08b7d443d1b5269",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 63792,
"upload_time": "2023-11-02T17:34:31",
"upload_time_iso_8601": "2023-11-02T17:34:31.379779Z",
"url": "https://files.pythonhosted.org/packages/4c/33/6e57acd7865ae8ff29d845fe6dc85de3a7a323eef1f4ecc65b130f5ef655/pymsbuild_winui-0.0.1a8.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-02 17:34:31",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "zooba",
"github_project": "pymsbuild-winui",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pymsbuild-winui"
}