Windows Dev AppConsult articles https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/bg-p/WindowsDevAppConsult Windows Dev AppConsult articles Sun, 24 Oct 2021 16:01:50 GMT WindowsDevAppConsult 2021-10-24T16:01:50Z How Sphero Leveraged Windows App SDK for a Great User Experience https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-sphero-leveraged-windows-app-sdk-for-a-great-user-experience/ba-p/2824955 <P class="c1"><STRONG><SPAN class="c7">Introduction</SPAN></STRONG></P> <P class="c1"><SPAN class="c5"><A class="c10" href="#" target="_blank" rel="noopener">Sphero</A></SPAN><SPAN>&nbsp;is transforming K-12 education with </SPAN><SPAN>accessible</SPAN><SPAN>&nbsp;</SPAN><SPAN>tools</SPAN><SPAN>&nbsp;that encourage exploration, imagination, and </SPAN><SPAN>perseverance</SPAN><SPAN>&nbsp;through STEAM and computer science. &nbsp;</SPAN><SPAN class="c0">In addition to its pre-built coding robots and STEAM kits, a key part of this experience is the Sphero Edu app which gives students a platform for creating programs and completing educational activities with Sphero robots. &nbsp;</SPAN></P> <P class="c1 c2">&nbsp;</P> <P class="c1"><SPAN class="c0">Sphero had a version of the Sphero Edu for Windows devices, but it had several challenges:</SPAN></P> <UL class="c3 lst-kix_952j3xrlnjft-0 start"> <LI class="c1 c8 li-bullet-0"><SPAN class="c0">It was built with outdated technology and was difficult to maintain</SPAN></LI> <LI class="c1 c8 li-bullet-0"><SPAN class="c0">It did not support an offline experience</SPAN></LI> <LI class="c1 c8 li-bullet-0"><SPAN class="c0">It required users to login to create and save programs</SPAN></LI> <LI class="c1 c8 li-bullet-0"><SPAN class="c0">It wasn’t compatible with a new version of the Sphero Edu platform that was in development</SPAN></LI> </UL> <P class="c1 c2">&nbsp;</P> <P class="c1"><SPAN class="c0">Sphero decided to build a new version of the Sphero Edu app for Windows from scratch. &nbsp;The new app would need to deliver all the functionality in the old app that had been built with the investment of many developer-years of effort. &nbsp;But Sphero’s goal was to build the new app in a few months with only a few developers. &nbsp;To do this, Sphero decided to utilize the Windows App SDK.</SPAN></P> <P class="c1 c2">&nbsp;</P> <P class="c1"><STRONG><SPAN class="c4">A New Tool for a New App</SPAN></STRONG></P> <P class="c1"><SPAN class="c0">Building an app with cutting edge technology can be a challenge. &nbsp;Sphero was greatly aided by quick answers from Microsoft App Consult experts as questions arose. &nbsp;In turn, Sphero was able to provide feedback on the development experience of using the new technology. &nbsp;</SPAN></P> <P class="c1 c2">&nbsp;</P> <P class="c1"><SPAN class="c0">One of the critical value-adds of using Windows App SDK was that Sphero was able to reuse a significant amount of code from the prior Sphero Edu app. &nbsp;The user interface where kids assemble blocks to create programs for their robots and the layers of code that interpret those programs and send instructions to the robot could largely be re-used. &nbsp;This was critical for delivering within the time and staffing constraints.</SPAN></P> <P class="c1 c2">&nbsp;</P> <P class="c1"><SPAN class="c0">A Sphero Edu app utilizes a wide range of capabilities of a device. &nbsp;All Sphero apps require a Bluetooth connection to communicate with robots. &nbsp;Internet connectivity is important for access to educational content as well as storing programs on the Sphero Edu platform. &nbsp;Local storage is key for creating and storing programs on the device. &nbsp;The list goes on, but the key point is that Sphero was able to find a way to deliver all the critical functionality using the Windows App SDK. &nbsp;The payoff for the user, however, mostly comes from the enhanced experience.</SPAN></P> <P class="c1">&nbsp;</P> <P class="c1 c2"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="image3.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/316099iCBD86BEBBFC8EE40/image-size/large?v=v2&amp;px=999" role="button" title="image3.png" alt="image3.png" /></span></P> <P class="c1">&nbsp;</P> <P class="c1"><STRONG><SPAN class="c4">Creating a Better User Experience</SPAN></STRONG></P> <P class="c1"><SPAN class="c9 c11">The Sphero Edu platform is primarily used in classrooms. &nbsp;In this context, having technology that is simple, performant and reliable is especially important. &nbsp;Whereas in the prior version of the Sphero Edu app, a teacher would need to get all students through the process of logging in (and potentially needing to create accounts to be able to login), the </SPAN><SPAN class="c5 c9"><A class="c10" href="#" target="_blank" rel="noopener">new Sphero Edu app</A></SPAN><SPAN class="c0">&nbsp;allows students to dive in and start creating programs immediately. &nbsp;It has a very responsive UI and takes advantage of caching data locally to increase performance. &nbsp;The user interface takes advantage of the Windows App SDK’s ability to adapt to whatever screen size is available and works with both Windows 10 and 11 touch-enabled devices and keyboards. &nbsp;For schools where an internet connection is unavailable, the ability to use the app offline is critical. &nbsp;It’s a lot easier to inspire the creators of tomorrow when the tools fit their needs.</SPAN></P> <P class="c1 c2">&nbsp;</P> <P class="c1"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="image2.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/316100i71BFB942ABF3419B/image-size/large?v=v2&amp;px=999" role="button" title="image2.png" alt="image2.png" /></span></P> <P class="c1 c2">&nbsp;</P> <P class="c1 c2">&nbsp;</P> <P class="c1"><STRONG><SPAN class="c4">Conclusion</SPAN></STRONG></P> <P class="c1"><SPAN class="c0">Thanks to the Windows App SDK and great developer support from Microsoft, Sphero was able to quickly build a better version of a critical app in a very short timeframe.</SPAN></P> <P class="c1 c2">&nbsp;</P> <P class="c1"><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="image1.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/316101iF62664045FB0AEFA/image-size/large?v=v2&amp;px=999" role="button" title="image1.png" alt="image1.png" /></span></P> <P class="c1 c2">&nbsp;</P> <P class="c1 c2">&nbsp;</P> Tue, 12 Oct 2021 20:02:17 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-sphero-leveraged-windows-app-sdk-for-a-great-user-experience/ba-p/2824955 Mike Francis 2021-10-12T20:02:17Z How Mashreq bank is using React Native for Windows to bring new digital experiences to Windows https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-mashreq-bank-is-using-react-native-for-windows-to-bring-new/ba-p/2421056 <H3><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="MashreqLogo.jpg" style="width: 406px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/287556i372C7C2A558E6436/image-dimensions/406x423?v=v2" width="406" height="423" role="button" title="MashreqLogo.jpg" alt="MashreqLogo.jpg" /></span></H3> <P>&nbsp;</P> <H3 id="introduction">Introduction</H3> <P><A href="#" target="_blank" rel="noopener">Mashreq</A><SPAN>&nbsp;</SPAN>is the oldest regional bank based in UAE, with a strong presence in most GCC countries and a leading international network with offices in Asia, Europe, and United States. Based in Dubai, they have 15 domestic branches and 11 international ones, with more than 2,000 Windows devices deployed across all of them. Mashreq is a leader in digital transformation in the banking sector and, in 2019, they started digital transformation journey with Microsoft 365 and Dynamics 365 and switched later to Surface Pro device for their frontline employees.</P> <P>&nbsp;</P> <P>Mashreq bank developed an application<SPAN>&nbsp;</SPAN><STRONG>Universal Banker App</STRONG><SPAN>&nbsp;</SPAN>(UB App) with React Native to empower frontline employees working at branches to serve customers across a broad range of inquiries and journeys. The application helped them to increase the proximity with the customer, improving the customer experience and reducing the service time. After the success in one Business Unit, they decided to deploy the tool to all other business group, however they faced different challenges:</P> <P>&nbsp;</P> <OL> <LI>UB app was built for Android, while the other business group were using Windows PCs and Microsoft Surfaces.</LI> <LI>UB App was optimized for touch input, which is best for mobile devices, while other business group often work with a mouse &amp; keyboard setup.</LI> <LI>Mashreq development team had a limited experience with Windows native development.</LI> </OL> <P>Thanks to React Native for Windows, Mashreq was able to address all these challenges on a very short time and developed<SPAN>&nbsp;</SPAN><STRONG>Mashreq </STRONG><STRONG>FACE App for Windows platform</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FaceLogin.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/286766i64A95E95A96BF0DD/image-size/large?v=v2&amp;px=999" role="button" title="FaceLogin.png" alt="FaceLogin.png" /></span></P> <P>&nbsp;</P> <H3 id="reusing-the-existing-investments-to-deliver-a-first-class-experience-on-windows">Reusing the existing investments to deliver a first-class experience on Windows</H3> <P><A href="#" target="_blank" rel="noopener">React Native for Windows</A><SPAN>&nbsp;</SPAN>has enabled the development team to reuse the assets they build on React Native for the Android application. "<EM>React Native for Windows allowed us to extend the same experience of the original Android application with maximum code reusability</EM>" said Anubhav Jain, Digital Product Lead in Mashreq. Thanks to the modularity provided by React Native, the development team was able to build a native Windows experience tailored for the other business groups by utilizing the existing components they had built for the Android version.</P> <P>&nbsp;</P> <P>Thanks to the investments done by Microsoft on React Native, this is no longer a mobile-only cross-platform framework, but it’s a great solution to support cross-platform scenarios across desktop as well. This has enabled Mashreq to provide a first-class experience to the employees who are interacting with the application on Windows, whether if they are using PCs optimized for mouse and keyboard or departments who have adopted Microsoft Surface to provide a great touch experience.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dashboard.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/286769i0271C5C2E49FB4FC/image-size/large?v=v2&amp;px=999" role="button" title="Dashboard.png" alt="Dashboard.png" /></span></P> <P>&nbsp;</P> <P>React Native for Windows has enabled the development team at Mashreq to develop with their existing workforce and allowed them to iterate faster on the project targeting multiple platforms.</P> <P>&nbsp;</P> <P>React Native for Windows has enabled Mashreq not only to reuse their existing skills and code to bring their application to Windows, but also to enhance it by tailoring it with specific Windows features.</P> <P>&nbsp;</P> <P>As part of their digital transformation journey, Mashreq has also deployed Microsoft Teams as official communication app internally and for external customers. The development team has integrated Microsoft Teams in FACE App on Windows, by enabling employees to call customers directly with just one click by using Microsoft Teams communication capabilities.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="TeamsCalling.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/286770iE9E12F046B7D4EAE/image-size/large?v=v2&amp;px=999" role="button" title="TeamsCalling.png" alt="TeamsCalling.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Since React Native for Windows generates a native Windows application, it empowers developers to support a wide range of Windows-only scenarios, like interacting with specialized Hardware. This feature has enabled Mashreq to implement specialized biometric authentication in FACE. By connecting to various types of biometric devices (such as card readers, fingerprint readers, etc.)-Mashreq can validate customer information simply by scanning the ID card provided by the government and do the fingerprint scanning. By working closely with Microsoft, Mashreq has been able to integrate inside the Windows version of FACE application the components required to enable the biometric authentication process while complying with government regulations. This integration enables to securely process financial &amp; non-financial transactions.</SPAN></P> <P>&nbsp;</P> <H3 id="conclusion">Conclusion</H3> <P>Thanks to React Native for Windows, Mashreq will be able to seamlessly evolve Apps on Android and Windows while, at the same time, continue their digital transformation journey gradually adopting more Teams and Windows specific capabilities.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><LI-VIDEO vid="https://youtu.be/shhIDZvNj-c" align="center" size="medium" width="400" height="225" uploading="false" thumbnail="" external="url"></LI-VIDEO></P> Wed, 09 Jun 2021 18:44:54 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-mashreq-bank-is-using-react-native-for-windows-to-bring-new/ba-p/2421056 Matteo Pagani 2021-06-09T18:44:54Z Fujitsu Limited is helping to overcome communication barriers with WinUI and MSIX https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/fujitsu-limited-is-helping-to-overcome-communication-barriers/ba-p/2362832 <P><SPAN><A href="#" target="_blank" rel="noopener">Fujitsu Limited</A> is a Japanese multinational information and communications technology equipment and services company, established in 1935 and headquartered in Tokyo. </SPAN>&nbsp;They are a world-leading digital transformation partner. Using a wide portfolio of trusted technology services, solutions and products, they work with customers to co-create solutions that help them on their journey to enterprise-wide digitalization.</P> <P>&nbsp;</P> <P><SPAN>Fujitsu Limited operates on the global market and it provides services to companies all around the world. As such, the company realizes that communication barriers caused by different languages and disabilities can have a significant impact on the life of many people. With the impact of COVID-19, which severely reduced the ability to interact with people in-person, this problem has become more severe, due to the high volume of remote meetings and interactions.</SPAN></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="yoyamagu_1-1621299979294.png" style="width: 471px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/281415iA6E5F961B1675B44/image-dimensions/471x267?v=v2" width="471" height="267" role="button" title="yoyamagu_1-1621299979294.png" alt="yoyamagu_1-1621299979294.png" /></span></P> <P>&nbsp;</P> <P><STRONG><SPAN>Introducing LiveTalk</SPAN></STRONG></P> <P><SPAN>To overcome this challenge, Fujitsu Limited has developed an application called <A href="#" target="_blank" rel="noopener">LiveTalk</A>, which enables everyone to communicate without barriers. LiveTalk is able to instantly turn anyone’s conversation into text in real-time, no matter where they come from. LiveTalk, in fact, supports 43 languages, including Japanese and Chinese. It was designed with a real time voice recognition feature and real time subtitles for both in person meetings, as well as remote meetings and remote classrooms. It has been built to facilitate communication between deaf and hard of hearing people and hearing people, but its capabilities extend to real time language translation as well.</SPAN></P> <P>&nbsp;</P> <P><SPAN>The application is targeting a very broad audience: from very young children to adults; from people who are learning Japanese as a second language to people with disabilities. The breadth of this project has introduced a few challenges:</SPAN></P> <P>&nbsp;</P> <OL> <LI><SPAN><SPAN>Japanese is based on three writing systems: hiragana, katakana andkanji. To make Japanese easier to learn, especially for very young children or people who wants to learn it as second language, Japanese includes special characters called ruby, which indicate how to pronounce the text. This makes the operation to render Japanese text in the application quite complex, because words must be wrapped considering the space taken by the Ruby feature.<BR /></SPAN></SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="yoyamagu_3-1621300058805.png" style="width: 479px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/281417iC6FABA65E8923762/image-dimensions/479x222?v=v2" width="479" height="222" role="button" title="yoyamagu_3-1621300058805.png" alt="yoyamagu_3-1621300058805.png" /></span> <P>&nbsp;</P> </LI> </OL> <OL start="2"> <LI><SPAN>Accessibility is a key requirement. The application must provide features like support to high-contrast mode, so that it can be proficiently used also by people with disabilities.</SPAN></LI> <LI><SPAN>Since the audience can have different levels of familiarity with technology, the installation and update experience must be as simple as possible.</SPAN></LI> </OL> <P><SPAN>Fujitsu Limited was able to address all these challenges by leveraging <A href="#" target="_blank" rel="noopener">WinUI</A>, <A href="#" target="_blank" rel="noopener">MSIX</A> and <A href="#" target="_blank" rel="noopener">Xamarin</A> as foundation to the application.</SPAN></P> <P>&nbsp;</P> <P><STRONG><SPAN>Building a first-class experience with WinUI</SPAN></STRONG></P> <P><SPAN>WinUI was the natural choice to build a first-class experience on Windows, which could tackle all the challenges that the development team needed to address.</SPAN></P> <P><SPAN>WinUI provides a powerful and extensible UI system, which enables developers to tailor the user experience based on their needs. The flexibility of the XAML framework enabled Fujitsu Limited to customize the TextBlock control, by integrating their own custom algorithm to render Ruby characters.</SPAN></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="yoyamagu_4-1621300152001.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/281418iDFF025E89EA8FC59/image-size/medium?v=v2&amp;px=400" role="button" title="yoyamagu_4-1621300152001.png" alt="yoyamagu_4-1621300152001.png" /></span></P> <P>&nbsp;</P> <P><SPAN>&nbsp;</SPAN></P> <P><SPAN>The application is now able to recognize the speech of the user and convert it into text using the standard Japanese characters. WinUI will do all the heavy work to add the Ruby characters on top and take in account the different spacing.</SPAN></P> <P><SPAN>Another WinUI feature the development team took advantage of is built-in accessibility support. All the controls included in WinUI provides first-in-class accessibility features, by recognizing and adapting to the accessibility options that are available in Windows 10. Thanks to this feature, Fujitsu Limited has been quickly able to make the application theme aware, so that it can properly adapt to users who are using a light, dark or high contrast theme in Windows 10.</SPAN></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="yoyamagu_5-1621300152010.png" style="width: 446px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/281420i4C71A7038687E668/image-dimensions/446x251?v=v2" width="446" height="251" role="button" title="yoyamagu_5-1621300152010.png" alt="yoyamagu_5-1621300152010.png" /></span></P> <P>&nbsp;</P> <P><SPAN>In the end, due to the flexible nature of LiveTalk, WinUI was the perfect choice to provide a user experience that spans across all the different scenarios where the app is used: from a traditional PC controlled by mouse &amp; keyboard to a touch-enabled device like a 2-in-1 or a tablet. Thanks to the built-in support for multiple input experiences, Fujitsu Limited was able to quickly introduce specific modes to better support mouse, keyboard and touch.</SPAN></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="yoyamagu_6-1621300152016.png" style="width: 516px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/281419iBE4021451552061F/image-dimensions/516x290?v=v2" width="516" height="290" role="button" title="yoyamagu_6-1621300152016.png" alt="yoyamagu_6-1621300152016.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P><STRONG><SPAN>Deploy and update the application with confidence using MSIX</SPAN></STRONG></P> <P><SPAN>MSIX, the innovative deployment solution for desktop applications on Windows 10, has helped Fujitsu Limited to provide a seamless and simple solution for all their users: consumers, enterprises, schools, etc.</SPAN></P> <P><SPAN>By packaging the application with MSIX, Fujitsu Limited has published LiveTalk on the Microsoft Store, which enables a one-click experience to acquire the application. Additionally, thanks to the built-in automatic updates feature, users won’t have to take any manual action to make sure they’re using the latest and greatest version of LiveTalk. Windows 10 will take care of updating it automatically every time the Fujitsu Limited development team publishes a new version.</SPAN></P> <P>&nbsp;</P> <P><STRONG><SPAN>Reaching all the users with Xamarin</SPAN></STRONG></P> <P><SPAN>What makes LiveTalk a very powerful solution is its flexibility: it can be used during a live conference; during a remote meeting; or even just during an informal in-person chat with a colleague. As such, Fujitsu Limited needed to go outside the desktop to reach users wherever they are. Xamarin was the natural choice to bring the Windows application also to other platforms. By sharing a similar UI framework and the same .NET ecosystem as WinUI, Fujitsu Limited was able to reuse most of the investments they made to bring the application also on Android and iOS, including support for Ruby characters.</SPAN></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="yoyamagu_7-1621300921645.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/281421i1BF75FD27F273AB5/image-size/medium?v=v2&amp;px=400" role="button" title="yoyamagu_7-1621300921645.png" alt="yoyamagu_7-1621300921645.png" /></span></P> <P>&nbsp;</P> <P><EM>Fujitsu said "Microsoft understood our intentions better than we could and provided appropriate and specific advice. As a result, we were able to outperform our expectations for Ruby, minimize the impact of increased Ruby processing time, and plan that we can provide to the market with confidence. Regarding&nbsp;WinUI, the current situation and future vision became clear, and we were able to align Microsoft's&nbsp;WinUI&nbsp;roadmap with our product roadmap.</EM>&nbsp;"</P> <P>&nbsp;</P> <P><STRONG>Conclusion</STRONG></P> <P>Fujitsu Limited has now planned to align the product roadmap of LiveTalk with the roadmaps of WinUI and .NET. This choice has enabled the development team to have a clear plan on the evolution of the app. It will help them to continue researching new technologies for their product and to be confident that they will be able to quickly integrate all the latest enhancements in the Windows ecosystem.</P> <P>&nbsp;</P> <P><IFRAME src="https://www.youtube.com/embed/nlDFQeybAJ8" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></IFRAME></P> Wed, 19 May 2021 03:11:53 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/fujitsu-limited-is-helping-to-overcome-communication-barriers/ba-p/2362832 yoyamagu 2021-05-19T03:11:53Z Central Laser Facility uses WinUI and Uno Platform to envision a new control system for EPAC https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/central-laser-facility-uses-winui-and-uno-platform-to-envision-a/ba-p/2348929 <P><A href="#" target="_blank" rel="noopener">The Central Laser Facility</A> (CLF) carries out research using lasers to investigate a broad range of science areas, spanning physics, chemistry, and biology. Their suite of laser systems allows them to focus light to extreme intensities, to generate exceptionally short pulses of light, and to image extremely small features.</P> <P>&nbsp;</P> <P>The Central Laser Facility is currently building the Extreme Photonics Applications Centre (EPAC) in Oxfordshire, UK. EPAC is a new national facility to support UK science, technology, innovation and industry. It will bring together world-leading interdisciplinary expertise to develop and apply novel, laser based, non-conventional accelerators and particle sources which have unique properties.</P> <P>&nbsp;</P> <P>The software control team inside Central Laser Facility develops applications that enable scientists to monitor and communicate with a wide range of scientific instruments. For example, the application can be used to move a motorised mirror to direct the laser beam toward a target, to watch a camera feed showing the current status of the system, or to configure and record data from a suite of cutting-edge scientific instruments such as x-ray cameras or electron spectrometers. These applications aggregate data and controls for specific tasks that a user needs to undertake - say, point the laser at a new target - and present them in a single screen to avoid the need to individually access all the different hardware necessary to make that happen.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Windows_final.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/283435i365448AF3FE40D39/image-size/large?v=v2&amp;px=999" role="button" title="Windows_final.png" alt="Windows_final.png" /></span></P> <P><STRONG>The Challenge: Moving forward the control system for EPAC</STRONG></P> <P>&nbsp;</P> <P>As CLF started planning the design of a new control system for EPAC, their main goal was to tackle some of the challenges they were facing with the existing set of applications:</P> <P>&nbsp;</P> <UL> <LI>Minimise the adaptations needed to run on multiple operating systems. CLF currently supports Windows and Linux, with other platforms like Android and Web in planning.</LI> <LI>Maximize code reuse while, at the same time, creating a scalable user interface. Their applications need to scale from mobile devices to large displays placed around the facility.</LI> <LI>Support advanced graphical features, like themes for easily changing colour schemes; the palette needed for viewing a screen through laser goggles in a laboratory is different than one would use in a control room, where no goggles are necessary.</LI> </UL> <P>&nbsp;</P> <P><STRONG>The Solution</STRONG></P> <P>&nbsp;</P> <P>WinUI and <A href="#" target="_blank" rel="noopener">Uno Platform</A> were a perfect combination to tackle these challenges.</P> <P>WinUI provides a state-of-the-art UI platform, which offers the powerful rendering capabilities needed by the application to show the real time feed coming from the cameras; to generate complex graphs that display in real-time the data captured by the instruments; to adapt to different layouts and form factors; ultimately, to easily create easy-to-use experiences thanks to a wide range of modern controls with full support to accessibility and multiple input types. Uno Platform is enabling the Central Laser Facility to take these features which empower the experience built for Windows and run it with no or minimal code changes on all the other platforms targeted by Central Laser Facility: Linux, Android and Web.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Android_Final.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/280300i71EC2E5A24BFE806/image-size/large?v=v2&amp;px=999" role="button" title="Android_Final.png" alt="Android_Final.png" /></span></P> <P>&nbsp;</P> <P><EM>“Thanks to WinUI and Uno Platform, we were able to leverage the excellent set of developer tools that exists in .NET, and provides access to the reusable content in the Windows Community Toolkit and the XAML controls gallery.” </EM>shared Chris Gregory, Software Control Engineer. “<EM>The primary attraction for us was the ability to deploy applications cross-platform. This will allow us to visualise what's happening with our instrumentation on the Windows machines in the control room, the Linux systems running the back end, on a tablet inside the laboratory, or on a mobile device for off-site monitoring.</EM></P> <P>&nbsp;</P> <P><EM>This flexibility means that scientists and engineers can see a uniform presentation of the information they need no matter where they are in our facility, with minimal extra developer effort. Added to this, the availability of such a rich set of controls will result in the development of applications that are much more intuitive to use”.</EM></P> <P>&nbsp;</P> <P><EM><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Web_final.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/283437i1AA9A7988E22119B/image-size/large?v=v2&amp;px=999" role="button" title="Web_final.png" alt="Web_final.png" /></span></EM></P> Tue, 25 May 2021 08:47:58 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/central-laser-facility-uses-winui-and-uno-platform-to-envision-a/ba-p/2348929 Matteo Pagani 2021-05-25T08:47:58Z Kahua uses WinUI 3 – Reunion, Uno Platform and Azure https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/kahua-uses-winui-3-reunion-uno-platform-and-azure/ba-p/2341809 <P><A href="#" target="_blank" rel="noopener">Kahua</A> provides project management and collaboration software focused on real estate, engineering, construction, and operations industries. Kahua’s solution helps manage project and program costs, documents, and processes from inception through implementation to improve efficiency and reduce risk.</P> <P>&nbsp;</P> <P>Recently Kahua has been selected by <STRONG>US General Services Administration</STRONG> (GSA) Public Building Service for its new management information system to manage <STRONG>8600+ assets</STRONG>, with <STRONG>370 million square feet</STRONG> of workspace for <STRONG>1.1 million federal employees</STRONG> and preserve <STRONG>500+ historic properties</STRONG>.</P> <P>&nbsp;</P> <P><STRONG>The Challenge &amp; Technical Requirements</STRONG></P> <P>Kahua needed a future-proof solution which builds on its legacy desktop application, while using C#, XAML and Azure skillsets of its developers. Kahua had a short timeline and a recurring imperative to bring new features to market quickly, on different devices – from desktop to web and mobile. In addition, Kahua’s developers wanted to reduce the time to market by maintaining a single codebase application which prevents re-implementing the same functionality for different platforms.</P> <P>&nbsp;</P> <P>Due to the nature of managing access for users in high-security environments such as financial institutions and government agencies, security was a major requirement.&nbsp; Additionally, the solution had to enable accessibility and localization.</P> <P>&nbsp;</P> <P>For users, the UI had to be modern and intuitive, providing simple onboarding and consistent, immersive experience for users on all devices.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Kahua_Win10.jpg" style="width: 633px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/283636i5C515A085E52EBE0/image-dimensions/633x356?v=v2" width="633" height="356" role="button" title="Kahua_Win10.jpg" alt="Kahua_Win10.jpg" /></span></P> <P> </P> <P>&nbsp;</P> <P><STRONG>The Solution</STRONG></P> <P>Kahua selected <A href="#" target="_blank" rel="noopener">WinUI 3 – Reunion</A>, <A href="#" target="_blank" rel="noopener">Uno Platform</A> and <A href="#" target="_blank" rel="noopener">Azure</A> to rapidly develop and deploy a multi-platform solution.</P> <P>&nbsp;</P> <P>On Windows, Kahua uses WinUI 3 to deliver a delightful and modern user experience on Windows. To achieve a pure web experience, Kahua is utilizing the Uno Platform to provide a solution that is built with and runs on top of the Microsoft technology stack of Azure, .NET 5&amp;6, and WinUI 3. To reach additional platforms such as macOS, iOS and Android, Kahua is also using Uno Platform to provide user experiences specific to mobile devices and smaller form factors.</P> <P>&nbsp;</P> <P>By utilizing WinUI 3 – Reunion, Uno Platform and Azure, Kahua is meeting its requirements for security and accessibility. The Web application provides for a zero-installation experience, allowing IT departments to breathe easier and approve application updates without extensive investigation and review. Kahua can scale its operations quickly and reach users internationally.</P> <P>&nbsp;</P> <P>The users can access the solution on any device, be it through any modern browser or native app on the device of their choice.&nbsp; The user interface across devices is modern, familiar, and consistent as it is built with the same UI technology.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Robert_Evans_1-1620665572888.jpeg" style="width: 603px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/279493i86D36119899BDF62/image-dimensions/603x339?v=v2" width="603" height="339" role="button" title="Robert_Evans_1-1620665572888.jpeg" alt="Robert_Evans_1-1620665572888.jpeg" /></span></P> <P><STRONG>Code and Skill Reuse</STRONG></P> <P>The Kahua development team experienced 4X productivity compared to alternative solutions evaluated. New functionality is developed once, in a single codebase. The team benefited from a mature Windows developer ecosystem and skillset on hand.</P> <P>&nbsp;</P> <P>The Kahua development team was able to reuse a significant amount of the code from its legacy application, as well as utilize over 45 controls from WinUI and Windows Community Toolkit as well as 3rd party controls by <A href="#" target="_blank" rel="noopener">Syncfusion</A>.</P> <P>&nbsp;</P> <P>“By combining Microsoft WinUI 3 and Uno Platform we are able to provide our customers with features, functionality and security that is simply unachievable with any other solution” – said Colin Whitlatch, CTO of Kahua.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><LI-VIDEO vid="https://www.youtube.com/watch?v=aZrILH0cX1U" align="center" size="custom" width="581" height="581" uploading="false" thumbnail="https://i.ytimg.com/vi/aZrILH0cX1U/hqdefault.jpg" external="url"></LI-VIDEO></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> Tue, 25 May 2021 17:40:58 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/kahua-uses-winui-3-reunion-uno-platform-and-azure/ba-p/2341809 Robert_Evans 2021-05-25T17:40:58Z Passing installation parameters to a Windows application with MSIX and App Installer https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/passing-installation-parameters-to-a-windows-application-with/ba-p/1719829 <P>As a Windows developer, a very common requirement you might be asked to implement is to track information about the installation process. For example, let's say that you have started a special campaign to advertise your application and you want to understand how many people are installing it because they have clicked one of the promotional banners. Or, in an enterprise environment, you may want to know which is the department of the employee who is installing the application, so that you can apply a different configuration. A very common solution for this requirement is to leverage a web installation and to use query string parameters, appended to the installation URI, which must be collected by the Windows application the first time it's launched. For example, your installation URL can be something like<SPAN>&nbsp;</SPAN><STRONG><A href="#" target="_blank" rel="noopener">http://www.foo.com/setup?source=campaign</A></STRONG>. Then your application, when it's launched for the first time, must able to retrieve the value of the query string parameter called<SPAN>&nbsp;</SPAN><STRONG>source</STRONG><SPAN>&nbsp;</SPAN>and use it as it's needed (for example, by sending this information to an analytic platform like<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">App Center</A>).</P> <P>&nbsp;</P> <P>As you might know if you follow this blog, MSIX is the most modern technology to deploy Windows application and, through a feature called<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">App Installer</A>, you can support web installations with automatic updates. As such, is there a way to pass activation parameters to the application from the installer URL with MSIX and App Installer? The answer is yes!</P> <P>&nbsp;</P> <P>Let's see how we can do that.</P> <H3 id="adding-protocol-support">Adding protocol support</H3> <P>The way MSIX supports this feature is by leveraging<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">protocol support</A>. Your app must register a custom protocol, which will be used to launch the application after it has been installed from your website using App Installer. Then, your application will retrieve all the information about the activation through the startup arguments, like in a regular protocol activation scenario. For example, let's say you register a protocol called<SPAN>&nbsp;</SPAN><CODE>contoso-expenses:</CODE>. This means that, when someone invokes a URL like<SPAN>&nbsp;</SPAN><CODE>contoso-expenses:?source=campaign</CODE>, your application will receive as activation arguments the value<SPAN>&nbsp;</SPAN><CODE>source=campaign</CODE>. This is exactly what App Installer is going to do the first time it launches your MSIX packaged app after the installation has been completed.</P> <P>&nbsp;</P> <P>Adding protocol support in a MSIX packaged application is quite easy, thanks to the application manifest. In my scenario, I have a WPF application built with .NET Core, which is packaged as MSIX using the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Windows Application Packaging Project</A>. As such, all I have to do is to double click on the<SPAN>&nbsp;</SPAN><STRONG>Package.appxmanifest</STRONG><SPAN>&nbsp;</SPAN>file in the Windows Application Packaging Project and move to the<SPAN>&nbsp;</SPAN><STRONG>Declarations</STRONG><SPAN>&nbsp;</SPAN>section. In the<SPAN>&nbsp;</SPAN><STRONG>Available declarations</STRONG><SPAN>&nbsp;</SPAN>dropdown menu choose<SPAN>&nbsp;</SPAN><STRONG>Protocol</STRONG><SPAN>&nbsp;</SPAN>and fill the<SPAN>&nbsp;</SPAN><STRONG>Name</STRONG><SPAN>&nbsp;</SPAN>field with the name of the custom protocol you want to register (in my scenario, it's<SPAN>&nbsp;</SPAN><CODE>contoso-expenses</CODE>:(</img></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="protocol.png" style="width: 920px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/222530i287F362B59A2986B/image-size/large?v=v2&amp;px=999" role="button" title="protocol.png" alt="protocol.png" /></span></P> <P>&nbsp;</P> <H3 id="listening-for-activation-arguments">Listening for activation arguments</H3> <P>The next step is to make our application aware of activation arguments. The way you implement this support changes based on the development framework you have chosen. In this sample we're going to see how to do it in a WPF application. Activation arguments can be retrieved in the<SPAN>&nbsp;</SPAN><STRONG>App.xaml.cs</STRONG><SPAN>&nbsp;</SPAN>file, by overriding the<SPAN>&nbsp;</SPAN><CODE>OnStartup()</CODE><SPAN>&nbsp;</SPAN>method, which is invoked every time the application starts:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-24" class="language-csharp hljs"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">partial</SPAN> <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">App</SPAN> : <SPAN class="hljs-title">Application</SPAN> { <SPAN class="hljs-function"><SPAN class="hljs-keyword">protected</SPAN> <SPAN class="hljs-keyword">override</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">OnStartup</SPAN>(<SPAN class="hljs-params">StartupEventArgs e</SPAN>)</SPAN> { <SPAN class="hljs-keyword">string</SPAN> path = <SPAN class="hljs-string">$"<SPAN class="hljs-subst">{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}</SPAN>//AppInstaller.txt"</SPAN>; <SPAN class="hljs-keyword">if</SPAN> (e.Args.Length &gt; <SPAN class="hljs-number">0</SPAN>) { System.IO.File.WriteAllText(path, e.Args[<SPAN class="hljs-number">0</SPAN>]); } <SPAN class="hljs-keyword">else</SPAN> { System.IO.File.WriteAllText(path, <SPAN class="hljs-string">"No arguments available"</SPAN>); } } } </CODE></PRE> <P>The<SPAN>&nbsp;</SPAN><CODE>OnStartup()</CODE><SPAN>&nbsp;</SPAN>method gets, as input parameter, an object of type<SPAN>&nbsp;</SPAN><CODE>StartupEventArgs</CODE>, which includes any activation parameter that has been passed to the application. They are stored inside an array called<SPAN>&nbsp;</SPAN><CODE>Args</CODE>. In case of protocol activation, there will be only one item in the array with the full URL that has been used to activate the application. The previous sample code simply writes this information, for logging purposes, in a text file stored on the desktop of the current user.</P> <H3 id="test-the-custom-protocol-implementation">Test the custom protocol implementation</H3> <P>Before putting all together for App Installer, we have a way to quickly test if the implementation we have done works as expected. Since the App Installer implementation of this feature is based on the standard approach for managing custom protocols, we can test this scenario right away, without needing to package everything together and upload it on our website. We just need to invoke our custom protocol from any Windows shell.</P> <P>&nbsp;</P> <P>As first step, right click on the Windows Application Packaging Project and choose<SPAN>&nbsp;</SPAN><STRONG>Deploy</STRONG>, in order to install the application on the system and register the custom protocol. Now open the Run panel in Windows 10 (or just press Start+R on your keyboard) and type a URI which uses your custom protocol. For example, in my scenario I can use something like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-52" class="language-plaintext hljs">contoso-expenses:?source=campaign </CODE></PRE> <P>If you have implemented everything correctly, your application will start. The<SPAN>&nbsp;</SPAN><CODE>OnStartup()</CODE><SPAN>&nbsp;</SPAN>event will have been triggered as well, so if I check my desktop I will find a file called<SPAN>&nbsp;</SPAN><STRONG>AppInstaller.txt</STRONG><SPAN>&nbsp;</SPAN>with the following content:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-58" class="language-plaintext hljs">contoso-expenses:?source=campaign </CODE></PRE> <P>As you can see, everything is working as expected. The application has launched and, in the activation arguments, I've been able to get the full URL that it has been used to invoke the custom protocol.</P> <P>If you encounter issues during the<SPAN>&nbsp;</SPAN><CODE>OnStartup()</CODE><SPAN>&nbsp;</SPAN>event, the Windows Application Packaging Project gives you an option to easily test this scenario. Right click on it, move to the<SPAN>&nbsp;</SPAN><STRONG>Debug</STRONG><SPAN>&nbsp;</SPAN>section and enable the option<SPAN>&nbsp;</SPAN><STRONG>Do not launch, but debug my code when it starts</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Debug.png" style="width: 669px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/222531i8B2C02960DFCFD35/image-size/large?v=v2&amp;px=999" role="button" title="Debug.png" alt="Debug.png" /></span></P> <P>Now you can press F5 to launch the debugger, but the application won't be actively launched. The debugger will be on hold, waiting for the application to be activated. Thanks to this feature, you can easily test different activation paths. In our scenario, you can just add a breakpoint inside the<SPAN>&nbsp;</SPAN><CODE>OnStartup()</CODE><SPAN>&nbsp;</SPAN>method and then, from the Run panel, invoke the custom URL. The application will start and the debugger will wake up, triggering the breakpoint and giving you the option to debug your code.</P> <P>&nbsp;</P> <P>Now that we have a working implementation in our application, let's see how we can configure App Installer to leverage it.</P> <H3 id="setting-up-app-installer">Setting up App Installer</H3> <P>When it comes to the App Installer configuration, you don't have to do anything special to support this scenario. The App Installer file you're using today to deploy your MSIX application works fine. However, you will have to customize the URL that is used to trigger the installation. If you're generating the App Installer file as part of the publishing process in Visual Studio, you will end up with a web page similar to the following one:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ContosoExpenses.png" style="width: 703px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/222532i19F2759A60D168DE/image-size/large?v=v2&amp;px=999" role="button" title="ContosoExpenses.png" alt="ContosoExpenses.png" /></span></P> <P>The<SPAN>&nbsp;</SPAN><STRONG>Get the app</STRONG><SPAN>&nbsp;</SPAN>button will trigger the installation of the MSIX package by leveraging the special<SPAN>&nbsp;</SPAN><CODE>ms-appinstaller</CODE><SPAN>&nbsp;</SPAN>protocol and it will look like this:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-80" class="language-plaintext hljs">ms-appinstaller:?source=https://contosoexpensescd.z19.web.core.windows.net/ContosoExpenses.Package.appinstaller </CODE></PRE> <P>The trick here is to add a special parameter to this URI, called<SPAN>&nbsp;</SPAN><CODE>activationUri</CODE>. As such, open the web page with your favorite text editor and change the URL to look like this (or copy it directly in your web browser, if you just want to do a test):</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-86" class="language-plaintext hljs">ms-appinstaller:?source=https://contosoexpensescd.z19.web.core.windows.net/ContosoExpenses.Package.appinstaller&amp;activationUri=contoso-expenses:?source=campaign </CODE></PRE> <P>As you can see, I have added at the end an<SPAN>&nbsp;</SPAN><CODE>activationUri</CODE><SPAN>&nbsp;</SPAN>parameter with, as value, the same exact custom URL I have tested before, based on the<SPAN>&nbsp;</SPAN><CODE>contoso-expenses</CODE><SPAN>&nbsp;</SPAN>protocol registered in the application. When you do this, Windows will automatically launch this URL at the end of the MSIX deployment process, instead of simply launching your application.</P> <P>The installation process won't look different. When you click on<SPAN>&nbsp;</SPAN><STRONG>Get the app</STRONG><SPAN>&nbsp;</SPAN>button with the new custom URL, the user will continue to see the traditional MSIX deployment experience:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="InstallApp.png" style="width: 650px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/222533iB0B09CC6F6112D35/image-size/large?v=v2&amp;px=999" role="button" title="InstallApp.png" alt="InstallApp.png" /></span></P> <P>The only difference is that, as you can notice, the option<SPAN>&nbsp;</SPAN><STRONG>Launch when ready</STRONG><SPAN>&nbsp;</SPAN>is hidden. The application will be forcefully launched at the end of the process with the custom URL we have specified, otherwise we won't be able to get the activation parameters. Once the deployment is complete, Windows will silently invoke the custom URL we have passed to the<SPAN>&nbsp;</SPAN><CODE>activationUri</CODE><SPAN>&nbsp;</SPAN>parameter, which in our case is<SPAN>&nbsp;</SPAN><CODE>contoso-expenses:?source=campaign</CODE>. As a consequence, the experience will be like the one we have tested before when we have locally invoked the custom protocol: the application will be launched and, on the desktop, we'll find a file called<SPAN>&nbsp;</SPAN><STRONG>AppInstaller.txt</STRONG><SPAN>&nbsp;</SPAN>with the full URL that has been used for triggering the execution.</P> <P>&nbsp;</P> <P>Unfortunately, Visual Studio doesn't have a way to customize the web page that is generated to add this custom App Installer URI. However, in a real scenario, you won't probably use that page, but you will have the App Installer link embedded in a button or banner of your existing website.</P> <P>&nbsp;</P> <H3 id="working-with-the-activation-parameters">Working with the activation parameters</H3> <P>An easier way to work with the activation parameters is to leverage the<SPAN>&nbsp;</SPAN><CODE>HttpUtility</CODE><SPAN>&nbsp;</SPAN>class which is included in the<SPAN>&nbsp;</SPAN><CODE>System.Web</CODE><SPAN>&nbsp;</SPAN>namespace. Thanks to this class, you can manipulate the query string parameters coming from the Uri in an easier way:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-106" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">protected</SPAN> <SPAN class="hljs-keyword">override</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">OnStartup</SPAN>(<SPAN class="hljs-params">StartupEventArgs e</SPAN>)</SPAN> { <SPAN class="hljs-keyword">string</SPAN> path = <SPAN class="hljs-string">$"<SPAN class="hljs-subst">{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}</SPAN>//AppInstaller.txt"</SPAN>; <SPAN class="hljs-keyword">if</SPAN> (e.Args.Length &gt; <SPAN class="hljs-number">0</SPAN>) { UriBuilder builder = <SPAN class="hljs-keyword">new</SPAN> UriBuilder(e.Args[<SPAN class="hljs-number">0</SPAN>]); <SPAN class="hljs-keyword">var</SPAN> result = HttpUtility.ParseQueryString(builder.Query); <SPAN class="hljs-keyword">var</SPAN> <SPAN class="hljs-keyword">value</SPAN> = result[<SPAN class="hljs-string">"source"</SPAN>]; System.IO.File.WriteAllText(path, <SPAN class="hljs-string">$"Source: <SPAN class="hljs-subst">{<SPAN class="hljs-keyword">value</SPAN>}</SPAN>"</SPAN>); } <SPAN class="hljs-keyword">else</SPAN> { System.IO.File.WriteAllText(path, <SPAN class="hljs-string">"No arguments available"</SPAN>); } } </CODE></PRE> <P>As first step, we use the<SPAN>&nbsp;</SPAN><CODE>UriBuilder</CODE><SPAN>&nbsp;</SPAN>class to create a Uri object out of the activation parameter we have received inside the first position of the<SPAN>&nbsp;</SPAN><CODE>e.Args</CODE><SPAN>&nbsp;</SPAN>array. This way, we can easily access only to the query string parameters (without the whole<SPAN>&nbsp;</SPAN><CODE>contoso-expenses</CODE><SPAN>&nbsp;</SPAN>protocol) by using the<SPAN>&nbsp;</SPAN><CODE>Query</CODE><SPAN>&nbsp;</SPAN>property. By using the<SPAN>&nbsp;</SPAN><CODE>ParseQueryString()</CODE><SPAN>&nbsp;</SPAN>method and passing the query string we get in return a dictionary, which makes easy to access to all the various parameters. The previous sample shows how easily we can retrieve the value of the<SPAN>&nbsp;</SPAN><CODE>source</CODE><SPAN>&nbsp;</SPAN>parameter from the query string. Thanks to this approach, we can avoid to use string manipulation to retrieve the same information, which is more error prone.</P> <P>&nbsp;</P> <H3 id="what-about-a-universal-windows-platform-application">What about a Universal Windows Platform application?</H3> <P>What if, instead of a .NET application like in this example, I have a UWP app? From a custom protocol registration perspective, the approach is exactly the same. The deployment technology for UWP is MSIX, so also in this case we have a manifest file where we can register our protocol. From a code perspective, instead, UWP offers a dedicated method for custom activation paths, called<SPAN>&nbsp;</SPAN><CODE>OnActivated()</CODE>, which must be declared in the<SPAN>&nbsp;</SPAN><STRONG>App.xaml.cs</STRONG><SPAN>&nbsp;</SPAN>file. This is an example implementation:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-133" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">protected</SPAN> <SPAN class="hljs-keyword">override</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">OnActivated</SPAN>(<SPAN class="hljs-params">IActivatedEventArgs args</SPAN>)</SPAN> { <SPAN class="hljs-keyword">if</SPAN> (args <SPAN class="hljs-keyword">is</SPAN> ProtocolActivatedEventArgs eventArgs) { UriBuilder builder = <SPAN class="hljs-keyword">new</SPAN> UriBuilder(eventArgs.Uri); <SPAN class="hljs-keyword">var</SPAN> result = HttpUtility.ParseQueryString(builder.Query); <SPAN class="hljs-keyword">var</SPAN> <SPAN class="hljs-keyword">value</SPAN> = result[<SPAN class="hljs-string">"source"</SPAN>]; } } </CODE></PRE> <P>&nbsp;</P> <P>As you can see, the code is very similar to the WPF implementation. The only difference is that the<SPAN>&nbsp;</SPAN><CODE>OnActivated</CODE><SPAN>&nbsp;</SPAN>event can be triggered by multiple patterns and, as such, first we need to make sure that this is indeed a protocol activation, by checking the real type of the<SPAN>&nbsp;</SPAN><CODE>IActivataedEventArgs</CODE><SPAN>&nbsp;</SPAN>parameter. In case of protocol activation, it will be<SPAN>&nbsp;</SPAN><CODE>ProtocolActivatedEventArgs</CODE>, which gives us access to the to the<SPAN>&nbsp;</SPAN><CODE>Uri</CODE><SPAN>&nbsp;</SPAN>property we need to retrieve the activation URL.</P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>This implementation is based on the standard custom protocol support offered by Windows, so it's very easy to implement. Every development platform support a way to get activation parameters when the application starts, so you just need to write some code to manage the desired scenario.</P> <P>&nbsp;</P> <P>You can find the full sample used in this blog post<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on GitHub</A>.</P> <P>&nbsp;</P> <P>Happy coding!</P> <P>&nbsp;</P> <H3 id="references">References</H3> <UL id="pragma-line-155"> <LI id="pragma-line-155">App Installer:<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/msix/app-installer/app-installer-root</A></LI> <LI id="pragma-line-156">Protocol support in MSIX:<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-extensions#protocol</A></LI> <LI id="pragma-line-157">Windows Application Packaging Project:<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-packaging-dot-net</A></LI> <LI id="pragma-line-158">HttpUtility class:<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/dotnet/api/system.web.httputility?view=netcore-3.1</A></LI> </UL> Mon, 28 Sep 2020 12:26:20 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/passing-installation-parameters-to-a-windows-application-with/ba-p/1719829 Matteo Pagani 2020-09-28T12:26:20Z Running 16-bit applications on Windows 10 64-bit https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/running-16-bit-applications-on-windows-10-64-bit/ba-p/1671418 <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="monkeys.png" style="width: 322px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218496i4B743A35459D2A60/image-size/medium?v=v2&amp;px=400" role="button" title="monkeys.png" alt="monkeys.png" /></span></P> <P>&nbsp;</P> <P>I wrote this post as a <STRONG>proof of concept</STRONG> and as a best effort to make a 16-bit application run on Windows 10 64-bit.</P> <P>&nbsp;</P> <P>It will be demonstrated how to use a third-part open source framework called <STRONG>otya128 – winevdm</STRONG>, how to use <STRONG>MSIX</STRONG>, <STRONG>UWP Execution Alias</STRONG> and <STRONG>Packaging Support Framework</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="prohibit.png" style="width: 141px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218499i32628AC0C9FC1C78/image-dimensions/141x129?v=v2" width="141" height="129" role="button" title="prohibit.png" alt="prohibit.png" /></span></P> <P>&nbsp;</P> <P><U>Please notice that Microsoft recommends using virtualization or 32-bit machines to run 16-bit applications.</U></P> <P>&nbsp;</P> <P>This post is only a proof of concept and for informational and training purposes only and are provided "as is" without warranty of any kind, whether express or implied.</P> <P>&nbsp;</P> <H2>Download the 16-bit emulator</H2> <P>&nbsp;</P> <P>As 64-bit operating system does not have support for the <A title="Windows NT DOS Virtual Machine (NTVM)" href="#" target="_blank" rel="noopener"><STRONG>Windows NT DOS Virtual Machine (NTVM)</STRONG></A> system component, the first step is to download the 16-bit emulator called <A href="#" target="_blank" rel="noopener"><STRONG>otya128 – winevdm</STRONG></A> that is open source and available on <STRONG>GitHub:</STRONG></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="github.png" style="width: 137px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218500iD0B5C48637098129/image-size/medium?v=v2&amp;px=400" role="button" title="github.png" alt="github.png" /></span></P> <P>&nbsp;</P> <P class="lia-align-center"><A href="#" target="_blank" rel="noopener">https://github.com/otya128/winevdm</A></P> <P>&nbsp;</P> <P>You can clone the repository and build yourself or download the build artifact from the <STRONG>AppVeyor</STRONG> repository&nbsp;&nbsp;<A href="#" target="_blank" rel="noopener">https://ci.appveyor.com/project/otya128/winevdm/history</A></P> <P>&nbsp;</P> <P>If you opt to download the app from the <STRONG>AppVeyor</STRONG> repository, you need to choose what build you want:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="winevdm01.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218548iF15B7F74A34082D5/image-size/large?v=v2&amp;px=999" role="button" title="winevdm01.png" alt="winevdm01.png" /></span></P> <P>&nbsp;</P> <P>Select the job:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="winevdm02.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218551iC27A68B6998D3BEB/image-size/large?v=v2&amp;px=999" role="button" title="winevdm02.png" alt="winevdm02.png" /></span></P> <P>&nbsp;</P> <P>And finally click on artifacts to download the artifact file:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="winevdm03.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218552i9A8A53E1D13ACFCD/image-size/large?v=v2&amp;px=999" role="button" title="winevdm03.png" alt="winevdm03.png" /></span></P> <P>&nbsp;</P> <P>Now, all you must do is to extract the files to a folder of your preference. I will extract the files in the following folder of the root of my C drive:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="powershell">&nbsp;C:\otvdm-master-1846</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2>Installing Microsoft Visual Basic 3.0 IDE on Windows 10 64-bit</H2> <P>&nbsp;</P> <P>If you try to run the Visual Basic 3 installer (setup.exe), you will receive the following message telling that the application cannot run, since it is a 16-bit application and that the <STRONG>NTVDM is not available</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="this app cannot run.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218553iBD6B73BD64FB4876/image-size/large?v=v2&amp;px=999" role="button" title="this app cannot run.png" alt="this app cannot run.png" /></span></P> <P>&nbsp;</P> <P>We need to run the <STRONG>otvdm.exe</STRONG> passing as argument the application that we want to launch, in our case, the setup.exe of Visual Basic 3.0:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="powershell">C:\otvdm-master-1846\otvdm.exe "C:\VB\VB.EXE"</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Notice that now the Visual Basic 3.0 16-bit installer has been successfully launched:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="setup1.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218554i9348D7861CF0DD05/image-size/large?v=v2&amp;px=999" role="button" title="setup1.png" alt="setup1.png" /></span></P> <P>&nbsp;</P> <P>We can proceed with the installation:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="setup2.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218555i8FC9AF93DB284D4A/image-size/large?v=v2&amp;px=999" role="button" title="setup2.png" alt="setup2.png" /></span></P> <P>&nbsp;</P> <P>The default destination folder is <STRONG>C:\VB</STRONG>:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Setup3.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218556i5BEE60A514F1B603/image-size/large?v=v2&amp;px=999" role="button" title="Setup3.png" alt="Setup3.png" /></span></P> <P>&nbsp;</P> <P>There are no registry keys, and all the files will be copied to the C:\VB directory:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="setup4.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218557i99C2B3764B4EB62E/image-size/large?v=v2&amp;px=999" role="button" title="setup4.png" alt="setup4.png" /></span></P> <P>&nbsp;</P> <P>Done!!!! Visual Basic is successfully installed:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="setup5.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218558i32949CC37E430556/image-size/large?v=v2&amp;px=999" role="button" title="setup5.png" alt="setup5.png" /></span></P> <P>&nbsp;</P> <P>Click on <STRONG>Run</STRONG> <STRONG>Visual Basic 3.0</STRONG> to launch VB3 on Windows 10 64-bit:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="vb3 on win 10.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218559iBAE2DD6E45C6A5C7/image-size/large?v=v2&amp;px=999" role="button" title="vb3 on win 10.png" alt="vb3 on win 10.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Notice that despite of Visual Basic 3 is available on Start menu, <STRONG>you can’t directly launch the app</STRONG> from there, as the app is 16-bit:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="start menu.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218560i1D66129430162299/image-size/large?v=v2&amp;px=999" role="button" title="start menu.png" alt="start menu.png" /></span></P> <P>&nbsp;</P> <P>In that case you need to run the following command:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="powershell">C:\otvdm-master-1846\otvdm.exe "C:\VB\VB.EXE"</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2>Improving the app distribution and providing a better user experience through MSIX</H2> <P>&nbsp;</P> <P>Let’s see how <A title="MSIX" href="#" target="_blank" rel="noopener"><STRONG>MSIX</STRONG> </A>can simplify the application deployment by keeping together the 16-bit emulator and VB3 application in a single MSIX installation file. The <STRONG>installation will be reduced to a single click</STRONG> action that will install the App in <STRONG>less than 10 seconds</STRONG>.</P> <P>&nbsp;</P> <P>At the same time MSIX will provide a better user experience, <STRONG>allowing users or other apps to call the application executable</STRONG>, in this case, VB.EXE without having to specify the <STRONG>otvdm</STRONG> emulator. This is possible, because once packaged, the App will be access to the UWP manifest that allows us to create an <A title="execution alias" href="#" target="_blank" rel="noopener">execution alias</A>.</P> <P>&nbsp;</P> <H2>MSIX Packaging Tool</H2> <P>The first step is to download and install the <STRONG><A title="MSIX Packaging Tool" href="#" target="_blank" rel="noopener">MSIX Packaging Tool</A>, </STRONG>that is free and available on Microsoft Store, to allow us to package our App:</P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="noopener">https://www.microsoft.com/store/productId/9N5LW3JBCXKF</A></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt store.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218561iD771E168C5CE8342/image-size/large?v=v2&amp;px=999" role="button" title="mpt store.png" alt="mpt store.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Before launching the <STRONG>MSIX Packaging Tool</STRONG>, move the emulator and VB folders to another folder, like <STRONG>C:\setup\</STRONG>:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="setup folder.png" style="width: 251px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218562i81705470A01558E6/image-size/large?v=v2&amp;px=999" role="button" title="setup folder.png" alt="setup folder.png" /></span></P> <P>&nbsp;</P> <P>This step is need, as <STRONG>MSIX Packaging Tool (MPT)</STRONG>&nbsp;will monitor the changes made on the computer environment. So, as we already have VB3 installed and we don't want to install it again, I will just copy these two folders to their final destinations at the moment that the MPT will be monitoring the modifications.</P> <P>&nbsp;</P> <P>Once installed, open the <STRONG>MSIX Packaging Tool</STRONG> and click on <STRONG>Application package</STRONG> to create a new package for the application:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt1.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218563iBC9BEDFBEFE0599D/image-size/large?v=v2&amp;px=999" role="button" title="mpt1.png" alt="mpt1.png" /></span></P> <P>&nbsp;</P> <P>The <STRONG>MSIX Packaging Tool</STRONG> will monitor the changes made on the environment. To perform the packaging, it is possible to use a virtual machine or the physical machine itself to install the application.</P> <P>&nbsp;</P> <P>The best scenario is to use a virtual machine allowing you to reproduce the same process if necessary. It is important that the machine used to install the application does not contain the applications and their previously installed components.</P> <P>&nbsp;</P> <P>Choose the desired scenario and click the <STRONG>Next</STRONG> button:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt1a.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218568i5B685D29C41BB998/image-size/large?v=v2&amp;px=999" role="button" title="mpt1a.png" alt="mpt1a.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In this step, the tool will verify that the <STRONG>MSIX Packaging Tool Driver</STRONG> is installed and will disable Windows Update to decrease the number of changes to the operating system:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt2a.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218569i09B1603EFCFFB42C/image-size/large?v=v2&amp;px=999" role="button" title="mpt2a.png" alt="mpt2a.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Click on the <STRONG>Next</STRONG> button.</P> <P>&nbsp;</P> <P>Only MSIX packages signed with a valid certificate can be installed on Windows 10 machines.</P> <P>You can create a self-signed certificate for testing purposes, using the following PowerShell command:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="powershell">function CreateCertificate($name, $path) { Set-Location Cert:\LocalMachine\My New-SelfSignedCertificate -Type Custom -Subject "CN=$name" -KeyUsage DigitalSignature -FriendlyName $name -CertStoreLocation "Cert:\LocalMachine\My" $cert = Get-ChildItem "Cert:\LocalMachine\My" | Where Subject -eq "CN=$name" $pwd = ConvertTo-SecureString -String DefineTheCertificatePasswordHere@2020 -Force -AsPlainText Export-PfxCertificate -cert $cert.Thumbprint -FilePath "$path.pfx" -Password $pwd Export-Certificate -Cert $cert -FilePath "$path.cer" Move-Item -Path $cert.PSPath -Destination "Cert:\LocalMachine\TrustedPeople" } CreateCertificate "luisdem" "cert" </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>To execute the command, open <STRONG>PowerShell_ISE.exe</STRONG> and press <STRONG>F5</STRONG>. The command creates the test certificate and copies it to the <STRONG>Trusted People</STRONG> folder.</P> <P>&nbsp;</P> <P>It is important to notice that this test certificate should only be used to approve the application in a test environment. To distribute the application.</P> <P>&nbsp;</P> <P>Now, that we have a certificate, we can inform the certificate to have the final MSIX package file automatically signed. It is not necessary to provide the installer file, as the tool monitors all changes made to the operating system.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="mpt2.png" style="width: 984px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218567i5BA8DA5F5DF79D19/image-size/large?v=v2&amp;px=999" role="button" title="mpt2.png" alt="mpt2.png" /></span></P> <P>&nbsp;</P> <P>Click on the <STRONG>Next</STRONG> button.</P> <P>&nbsp;</P> <P>In this step, it is necessary to provide information such as the name of the package, the description that will be displayed to the user during installation, the name of the supplier (must be the same as the certificate) and the version number. Fill in the requested information:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt3.png" style="width: 984px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218570i390DEE6CA01A3CDC/image-size/large?v=v2&amp;px=999" role="button" title="mpt3.png" alt="mpt3.png" /></span></P> <P>&nbsp;</P> <P>Click on the <STRONG>Next</STRONG> button.</P> <P>&nbsp;</P> <P>Is in this step that we need to install the application.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt5.png" style="width: 984px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218571iCB184C101EF09232/image-size/large?v=v2&amp;px=999" role="button" title="mpt5.png" alt="mpt5.png" /></span></P> <P>&nbsp;</P> <P>As we already have the VB3 installed, this is the moment to moving back the emulator and VB3 folders to the drive C root:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="emulator and vb3 folders.png" style="width: 347px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218572i46689F85F92D7206/image-size/large?v=v2&amp;px=999" role="button" title="emulator and vb3 folders.png" alt="emulator and vb3 folders.png" /></span></P> <P>&nbsp;</P> <P>Click on the <STRONG>Next</STRONG> button only after completing the application installation and all settings.</P> <P>&nbsp;</P> <P>On the next screen it is necessary to define which applications will be visible in the start menu.</P> <P>As VB3 is a 16-bit application that depends on the 16-bit emulator, select only the otvdm.exe, as follows:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt6.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218573i5E47B5A1D09F7C98/image-size/large?v=v2&amp;px=999" role="button" title="mpt6.png" alt="mpt6.png" /></span></P> <P>&nbsp;</P> <P>Notice that is not possible to provide the arguments for the otvdm.exe, i.e., &nbsp;<STRONG>C:\VB\VB.EXE</STRONG>. For now, we can ignore the arguments as we are fix that later.</P> <P>&nbsp;</P> <P>Follows the expected result so far:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt7.png" style="width: 984px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218574i4AF78A349C0528E6/image-size/large?v=v2&amp;px=999" role="button" title="mpt7.png" alt="mpt7.png" /></span></P> <P>&nbsp;</P> <P>Click on the <STRONG>Next</STRONG> button.</P> <P>&nbsp;</P> <P>The following screen is displayed at the end of the installation and asks for confirmation if the monitoring can be ended:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt7 antes.png" style="width: 488px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218575i1ACFAA87A7898995/image-size/large?v=v2&amp;px=999" role="button" title="mpt7 antes.png" alt="mpt7 antes.png" /></span></P> <P>&nbsp;</P> <P>Click the Next button, as the application does not install any services:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt8.png" style="width: 984px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218576iBD53592ED3DD94A5/image-size/large?v=v2&amp;px=999" role="button" title="mpt8.png" alt="mpt8.png" /></span></P> <P>&nbsp;</P> <P>At this point, it is necessary to inform where the package will be generated, as well as it is possible to edit the package before saving it.</P> <P>&nbsp;</P> <P>Click on the <STRONG>Package editor</STRONG> button to check the package structure, as follows:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt9.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218577i96A725896D4DAF72/image-size/large?v=v2&amp;px=999" role="button" title="mpt9.png" alt="mpt9.png" /></span></P> <P>&nbsp;</P> <P>Click on <STRONG>Open file</STRONG> to check how the manifest was generated:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt9a.png" style="width: 954px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218578i29ED7171EE144596/image-size/large?v=v2&amp;px=999" role="button" title="mpt9a.png" alt="mpt9a.png" /></span></P> <P>&nbsp;</P> <P>It is possible to edit the manifest information, such as processor architecture, application description, minimum supported version of Windows 10 and others.</P> <P>&nbsp;</P> <P>Change the <STRONG>ProcessorArchitecture</STRONG> to <STRONG>“x86”,</STRONG> the <STRONG>DisplayName</STRONG> and <STRONG>Description</STRONG> attributes to “<STRONG>Microsoft Visual Basic 3.0</STRONG>”:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="manifest1.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218579i8EE3929FAF30E138/image-size/large?v=v2&amp;px=999" role="button" title="manifest1.png" alt="manifest1.png" /></span></P> <P>&nbsp;</P> <P>Notice that the Executable attribute doesn’t have the <STRONG>C:\otvdm-master-1846</STRONG> path, but instead it has the value <STRONG>VFS\AppVPackageDrive\otvdm-master-1846</STRONG>. The <STRONG>AppVPackageDrive</STRONG> is the folder inside the package that corresponds to the drive C root. The application will look for the file on C:\otvdm-master-1846 but it will be redirected to the folder inside the package (VFS\AppVPackageDrive\otvdm-master-1846).</P> <P>&nbsp;</P> <P>The only problem here, is that is no possible to pass arguments in this manifest file. So, we will need to use <A title="Package Support Framework (PSF)" href="#" target="_blank" rel="noopener"><STRONG>Package Support Framework (PSF)</STRONG></A> to fix that.</P> <P>&nbsp;</P> <P>For now, just change the Executable value path to “<STRONG>PsfLauncher32.exe</STRONG>” that is part of the PSF.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="manifest2.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218580i8C8CAD32AA1BE788/image-size/large?v=v2&amp;px=999" role="button" title="manifest2.png" alt="manifest2.png" /></span></P> <P>&nbsp;</P> <P>We will now create an <STRONG>execution alias</STRONG> to allow launching the application by typing or calling the VB.EXE command from anywhere. To do this, add the following lines just below the line <STRONG>&lt;/uap:VisualElements&gt;</STRONG>:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="json">&lt;Extensions&gt; &lt;uap3:Extension Category="windows.appExecutionAlias" Executable="PsfLauncher32.exe" EntryPoint="Windows.FullTrustApplication"&gt; &lt;uap3:AppExecutionAlias&gt; &lt;desktop:ExecutionAlias Alias="VB.EXE" /&gt; &lt;/uap3:AppExecutionAlias&gt; &lt;/uap3:Extension&gt; &lt;/Extensions&gt;</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Follows how the manifest file must be defined:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="manifest3.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218581iADF36A360B1939F9/image-size/large?v=v2&amp;px=999" role="button" title="manifest3.png" alt="manifest3.png" /></span></P> <P>&nbsp;</P> <P><STRONG>Save</STRONG>&nbsp;and <STRONG>close</STRONG>&nbsp;the manifest (Notepad file) to unblock the MSIX Packaging Tool editor screen.</P> <P>&nbsp;</P> <P>Click on <STRONG>Package files</STRONG> and check if all the files and directories listed are used by the application.</P> <P>Remove unnecessary files or directories to reduce the package size:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt9b.png" style="width: 984px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218582iA87A4AA39BF42129/image-size/large?v=v2&amp;px=999" role="button" title="mpt9b.png" alt="mpt9b.png" /></span></P> <P>&nbsp;</P> <P>It is at this step that we need to add the Package Support Framework files, to allow the <STRONG>PsfLauncher32.exe</STRONG> calling the emulator passing the arguments.</P> <P>&nbsp;</P> <P>To proceed, download the PSF files (<STRONG>PSFBinaries.zip</STRONG>) available on GitHub:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="github.png" style="width: 137px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218500iD0B5C48637098129/image-size/medium?v=v2&amp;px=400" role="button" title="github.png" alt="github.png" /></span></P> <P class="lia-align-center"><A href="#" target="_blank" rel="noopener">Releases · microsoft/MSIX-PackageSupportFramework (github.com)</A></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="psf files.png" style="width: 970px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218583i579AC00ADC4AB86A/image-size/large?v=v2&amp;px=999" role="button" title="psf files.png" alt="psf files.png" /></span></P> <P>&nbsp;</P> <P>And extract the contents to a folder of your preference. I will extract to C:\PSF:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="PSF2.png" style="width: 407px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218584i0D5FDBD2E71B7BC3/image-size/large?v=v2&amp;px=999" role="button" title="PSF2.png" alt="PSF2.png" /></span></P> <P>&nbsp;</P> <P>In the same folder, create a <STRONG>config.json</STRONG> file with the following content:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="json">{ "applications": [ { "id": "OTVDM", "executable": "VFS/AppVPackageDrive/otvdm-master-1846/otvdmw.exe", "arguments": "AppVPackageDrive/VB/VB.EXE", "workingDirectory": "VFS" } ] }</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>The <STRONG>id</STRONG> corresponds to the Application Id defined in the application manifest, the <STRONG>executable</STRONG> contains the emulator path inside the package and the <STRONG>arguments</STRONG> the VB3 path inside the package.</P> <P>&nbsp;</P> <P>Switch back to <STRONG>MSIX Packaging Tool</STRONG>. In the Package file tab, right click on Package folder and click on</P> <P>&nbsp;</P> <P>Add file from the context menu:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt10.png" style="width: 983px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218585i37EDC849428F27C9/image-size/large?v=v2&amp;px=999" role="button" title="mpt10.png" alt="mpt10.png" /></span></P> <P>&nbsp;</P> <P>Add all the files that finish with 32 and the <STRONG>config.json</STRONG> files to the project:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt11.png" style="width: 984px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218586i635FA4E2851B28FF/image-size/large?v=v2&amp;px=999" role="button" title="mpt11.png" alt="mpt11.png" /></span></P> <P>&nbsp;</P> <P>Click on the <STRONG>Create</STRONG> button to generate the package and inform where the file should be generated.</P> <P>&nbsp;</P> <P>The following screen will display where the MSIX file was generated as well as the log file:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mpt12.png" style="width: 722px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218587i2FFB19755C3514C1/image-size/large?v=v2&amp;px=999" role="button" title="mpt12.png" alt="mpt12.png" /></span></P> <P>&nbsp;</P> <H2>Install the Visual Basic 3 packaged with MSIX</H2> <P>&nbsp;</P> <P>Double-click the package to install the application:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="install.png" style="width: 814px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218588i6549AB86153AB9FD/image-size/large?v=v2&amp;px=999" role="button" title="install.png" alt="install.png" /></span></P> <P>&nbsp;</P> <P>You can now launch the application directly from Start menu or typing vb.exe in a prompt command:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="final.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218589i2A704B4B6E9D5600/image-size/large?v=v2&amp;px=999" role="button" title="final.png" alt="final.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>This solution is compatible with <A href="#" target="_blank" rel="noopener"><STRONG>MSIX App Attach</STRONG></A> for <A href="#" target="_blank" rel="noopener"><STRONG>Windows Virtual Desktop</STRONG></A> on-premises or on Microsoft Azure.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="WVD.jpg" style="width: 800px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/218590i1D9607CAFFCF0082/image-size/large?v=v2&amp;px=999" role="button" title="WVD.jpg" alt="WVD.jpg" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>I hope you like it!!!</P> <P>&nbsp;</P> <P>&nbsp;</P> Thu, 17 Sep 2020 00:07:13 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/running-16-bit-applications-on-windows-10-64-bit/ba-p/1671418 luisdem 2020-09-17T00:07:13Z Solve MSIX packaging failure - "Error starting the MSIX packaging tool driver 0x80131500" https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/solve-msix-packaging-failure-quot-error-starting-the-msix/ba-p/1592024 <P>Recently I work on one MSIX packaging task to convert a traditional Win32 installer to .msix package with <A href="#" target="_self">MSIX packaging tool</A>, however always faced this kind of error message once start the packaging:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="freistli_0-1597634328435.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/212674iD0FEAF814BAF1ED9/image-size/medium?v=v2&amp;px=400" role="button" title="freistli_0-1597634328435.png" alt="freistli_0-1597634328435.png" /></span></P> <P>&nbsp;</P> <P>By checking the error log, it shows:</P> <P>&nbsp;</P> <P><SPAN>[8/14/2020 10:29:06 AM] [Error] Error monitoring: <STRONG>Insufficient system resources exist to complete the requested service </STRONG></SPAN></P> <P><SPAN>[8/14/2020 10:29:06 AM] [Debug] Getting environment object from %UserProfile%\AppData\Local\Packages\Microsoft.MsixPackagingTool_8wekyb3d8bbwe\LocalState\MsixGenerator.ConversionState.xml </SPAN></P> <P><SPAN>[8/14/2020 10:29:06 AM] [Error] Error Occurred: Microsoft.ApplicationVirtualization.Packaging.Sequencing.SequencerException: Insufficient system resources exist to complete the requested service ---&gt;</SPAN></P> <P><SPAN> Microsoft.ApplicationVirtualization.Packaging.MonitorException: Insufficient system resources exist to complete the requested service ---&gt; </SPAN></P> <P><SPAN>System.ComponentModel.Win32Exception: Insufficient system resources exist to complete the requested service at Microsoft.ApplicationVirtualization.Packaging.Tracing.TraceController.Start(String logfilePath) at Microsoft.ApplicationVirtualization.Packaging.TracingSubsystem.&lt;&gt;c__DisplayClass6_0.&lt;.ctor&gt;b__0() at System.EventHandler`1.Invoke(Object sender, TEventArgs e)</SPAN></P> <P>&nbsp;</P> <P>However my PC has enough RAM (free 20GB), with latest Windows 10 update. I tried restarting PC and it doesn't help as well. Don't think it is a resource limit issue. With this question, I used Windows Feedback [Windows + F] to raised a feedback. The response from Window team was quick and quite helpful.</P> <P>&nbsp;</P> <P>The error indeed failed when start new system event tracing sessions. These sessions can only be a limited amount of them system-wide, this limit is <STRONG>64</STRONG> by default, otherwise we will hit this&nbsp;<STRONG>ERROR_NO_SYSTEM_RESOURCES </STRONG>error.</P> <P>&nbsp;</P> <P>This article&nbsp;<A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-starttracew#return-value</A>&nbsp;gave two suggestions:</P> <P>&nbsp;</P> <P>1. Reboot machine</P> <P>2. E<SPAN>diting the&nbsp;</SPAN><STRONG>REG_DWORD</STRONG><SPAN>&nbsp;key at&nbsp;</SPAN><STRONG>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WMI\EtwMaxLoggers</STRONG><SPAN>. Permissible values are 32 through 256</SPAN></P> <P>&nbsp;</P> <P><SPAN>I didn't take the steps as some system trace sessions can start when machine is starting up, and some traces should be stopped if they are not necessary. I used commands "<A href="#" target="_self">logman</A> query -etc", "<A href="#" target="_self">tracelog</A> -l", the result shows a lot of running trace sessions (up to 50). Although they are not hit 64 limits, I thought reducing them will be definitely worth trying.</SPAN></P> <P>&nbsp;</P> <P><SPAN>After some quick research, I can see the <STRONG>Perfmon Monitor</STRONG> will be helpful to manage the running system trace session easily. After taking below steps, the MSIX error is resolved immediately:</SPAN></P> <P>&nbsp;</P> <P><SPAN>1. In Start command, type "Performance Monitor", start it as Admin</SPAN></P> <P>&nbsp;</P> <P>2. Choose <STRONG>Data Collector Sets</STRONG> -&gt; <STRONG>Event Trace Sessions</STRONG>, right click to stop some sessions</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="freistli_1-1597638741569.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/212677i10E9D90DED0211DF/image-size/medium?v=v2&amp;px=400" role="button" title="freistli_1-1597638741569.png" alt="freistli_1-1597638741569.png" /></span></P> <P>&nbsp;</P> <P>3. To avoid this issue from next rebooting, can <STRONG>disable</STRONG> some of them in&nbsp;<STRONG>Data Collector Sets</STRONG> -&gt; <STRONG>Startup&nbsp;Event Trace Sessions</STRONG></P> <P><SPAN>&nbsp; &nbsp;</SPAN></P> <P><SPAN>&nbsp;Hope this helps!</SPAN></P> <P>&nbsp;</P> <P>&nbsp;</P> Mon, 17 Aug 2020 04:32:57 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/solve-msix-packaging-failure-quot-error-starting-the-msix/ba-p/1592024 freistli 2020-08-17T04:32:57Z Running WSL GUI Apps on Windows 10 https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/running-wsl-gui-apps-on-windows-10/ba-p/1493242 <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="logo.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201595iDF88424822733A2A/image-size/large?v=v2&amp;px=999" role="button" title="logo.png" alt="logo.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In this post I will demonstrate how to run <STRONG>Linux GUI</STRONG> (Graphical User Interface) applications on Windows Desktop platform.</P> <P>&nbsp;</P> <P>For now, it is necessary to install a third-party App to run the GUI Apps, but Microsoft announced on <STRONG>//build 2020</STRONG> that they will release soon an improvement that will not require any third-party component to run Linux GUI Apps on Windows Desktop.</P> <P>&nbsp;</P> <P>Pre-requirements:</P> <UL> <LI>Windows 10</LI> <LI>WSL</LI> </UL> <P>&nbsp;</P> <P>If you want to know <STRONG>how to install WSL on Windows 10</STRONG>, please check the following post:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="logo.png" style="width: 200px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200183iF6B0A9312DF0B6E4/image-size/small?v=v2&amp;px=200" role="button" title="logo.png" alt="logo.png" /></span><A title="Using WSL2 in a Docker Linux container on Windows to run a Minecraft Java Server" href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/using-wsl2-in-a-docker-linux-container-on-windows-to-run-a/ba-p/1482133" target="_blank" rel="noopener">Using WSL2 in a Docker Linux container on Windows to run a Minecraft Java Server</A></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>It will be demonstrated the three ways to run the Linux GUI Apps on Windows 10 through:</P> <UL> <LI>VcXsrv Windows X Server (free)</LI> <LI>X410 App available on Microsoft Store (paid app)</LI> <LI>Kali App available on Microsoft Store (free)</LI> </UL> <H3>&nbsp;</H3> <H3>First option: VcXsrv Windows X Server</H3> <P>The X server is a provider of graphics resources and keyboard/mouse events. I am using the <STRONG>VcXsrv Windows X Server </STRONG>that is open-source and is frequently update.</P> <P>&nbsp;</P> <P>The first step is to install the third-part display manager called <STRONG>VcXsrv Windows X Server</STRONG> available at:</P> <P><A href="#" target="_blank" rel="noopener">https://sourceforge.net/projects/vcxsrv/</A></P> <P>&nbsp;</P> <P>During setup is important to disable the access control to avoid the permission denied error when trying to run a GUI application:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="VcXsrv disable access control 2.png" style="width: 508px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201596iBCB2B8DA889830E0/image-size/large?v=v2&amp;px=999" role="button" title="VcXsrv disable access control 2.png" alt="VcXsrv disable access control 2.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>To warranty that the “<STRONG>Disable access control</STRONG>” will be always checked, save the configuration and always launch <STRONG>VcXsrv </STRONG>using the configuration file (<STRONG>config.xlaunch</STRONG>) :</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="VcXsrv disable access control.png" style="width: 501px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201597iFA9AF150A87FC588/image-size/large?v=v2&amp;px=999" role="button" title="VcXsrv disable access control.png" alt="VcXsrv disable access control.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>NOTE: Don't forget to allow <STRONG>VcXsrv </STRONG>in the Windows firewall settings.</P> <P>&nbsp;</P> <P><SPAN>Windows Security -&gt; Firewall &amp; network protection -&gt; Allow an app through firewall -&gt; make sure VcXsrv has both public and private checked.</SPAN></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="firewall.png" style="width: 658px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/206521i564787EBD841432D/image-size/large?v=v2&amp;px=999" role="button" title="firewall.png" alt="firewall.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>The next step is to set the <STRONG>DISPLAY</STRONG> environment variable on Linux to use the Windows host's IP address as WSL2 and the Windows host are not in the same network device. It is necessary to set the DISPLAY environment variable with the correct IP address on launch. There are different ways to set the DISPLAY variable, thank you&nbsp;<LI-USER uid="879052"></LI-USER>&nbsp;and&nbsp;<LI-USER uid="851344"></LI-USER>&nbsp;for the updates.</P> <P>&nbsp;</P> <P>Follows some ways that you can choose:</P> <P>&nbsp;</P> <LI-CODE lang="bash">export DISPLAY="`grep nameserver /etc/resolv.conf | sed 's/nameserver //'`:0" export DISPLAY="`sed -n 's/nameserver //p' /etc/resolv.conf`:0" export DISPLAY=$(ip route|awk '/^default/{print $3}'):0.0</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Running the following command, it is possible to see that the $DISPLAY environment variable now has the Windows Host’s IP set:</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="bash">Echo $DISPLAY</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="display.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201610iA652D750738A7F90/image-size/large?v=v2&amp;px=999" role="button" title="display.png" alt="display.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>To avoid having to run that command every time that WSL is launched, you can include the command at the end of the <STRONG>/etc/bash.bashrc </STRONG>file:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="export display.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201611iC4DA5B9EC05A849E/image-size/large?v=v2&amp;px=999" role="button" title="export display.png" alt="export display.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P><STRONG>UPDATE:</STRONG> I would like to thank&nbsp;<LI-USER uid="931103"></LI-USER>&nbsp;for let me know that now it is also necessary to create a .xsession file in the user's home directory (/home/&lt;user&gt;/.xsession) with the content xfce4-session:</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="bash">echo xfce4-session &gt; ~/.xsession</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><STRONG>Done!</STRONG> Now you can run the Linux GUI Apps on Windows desktop.</P> <P>&nbsp;</P> <H2>Let’s try this out!</H2> <P>&nbsp;</P> <P>Follows some Apps that you can use to test:</P> <P>&nbsp;</P> <P>Install Chromium Dev :</P> <P>&nbsp;</P> <LI-CODE lang="bash">sudo add-apt-repository ppa:saiarcot895/chromium-dev sudo apt-get update sudo apt-get install chromium-browser</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Install GEDIT:</P> <P>&nbsp;</P> <LI-CODE lang="bash">sudo apt install gedit gedit</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Install x11-apps:</P> <P>&nbsp;</P> <LI-CODE lang="bash">sudo apt install x11-apps xeyes xcalc </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Make sure that <STRONG>XLaunch</STRONG> is running and before calling the Linux GUI Apps on Windows Desktop environment.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="apps running.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201642i8070DFD839A39F40/image-size/large?v=v2&amp;px=999" role="button" title="apps running.png" alt="apps running.png" /></span></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="run gui apps wsl.gif" style="width: 896px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201649i87F120274684BBEB/image-size/large?v=v2&amp;px=999" role="button" title="run gui apps wsl.gif" alt="run gui apps wsl.gif" /></span></P> <P>&nbsp;</P> <H2>&nbsp;</H2> <H2>What about running Windows 10 Apps and Linux GUI Apps in the same Desktop?</H2> <P>&nbsp;</P> <P>Run the following command to launch the <STRONG>xfce-panel</STRONG>:</P> <P>&nbsp;</P> <LI-CODE lang="bash">xfsettingsd --sm-client-disable; xfce4-panel --sm-client-disable --disable-wm-check &amp;</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2>What about accessing the Linux Desktop Environment via RDP?</H2> <P>&nbsp;</P> <P>The first thing that you need to do is to install a Linux Desktop Environment. I will user <A href="#" target="_blank" rel="noopener">Xfce</A> as it is a lightweight one.</P> <P>&nbsp;</P> <P>Run the following commands to install <STRONG>Xfce:</STRONG></P> <P>&nbsp;</P> <LI-CODE lang="bash">sudo apt install xfce4</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>The next step is to install the <A href="#" target="_blank" rel="noopener">xrdp</A> that provides a graphical login to remote machines using RDP (Microsoft Remote Desktop Protocol).</P> <P>&nbsp;</P> <LI-CODE lang="bash">sudo apt install xrdp</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Type the following command to get the <STRONG>WSL IP address</STRONG>:</P> <P>&nbsp;</P> <LI-CODE lang="bash">ip a</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ip addr.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201643i1943041F87253E3B/image-size/large?v=v2&amp;px=999" role="button" title="ip addr.png" alt="ip addr.png" /></span></P> <P>&nbsp;</P> <P>Make sure that <STRONG>xrdp</STRONG> service is running:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="start xrdp.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201644iCFCCE3924968129D/image-size/large?v=v2&amp;px=999" role="button" title="start xrdp.png" alt="start xrdp.png" /></span></P> <P>&nbsp;</P> <P>Run the <STRONG>Remote Desktop Client</STRONG> (MSTSC) and type the WSL IP address to connect to <STRONG>xfce4:</STRONG></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="mstsc.png" style="width: 407px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201645i6002D4337503E2F0/image-size/large?v=v2&amp;px=999" role="button" title="mstsc.png" alt="mstsc.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Done! Now you can access your favorite Linux IDE on WSL.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl rdp.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201655iBEACFB506DF446FD/image-size/large?v=v2&amp;px=999" role="button" title="wsl rdp.png" alt="wsl rdp.png" /></span></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl rdp 3.gif" style="width: 896px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/201659i5BCA3DA6EC90ADDF/image-size/large?v=v2&amp;px=999" role="button" title="wsl rdp 3.gif" alt="wsl rdp 3.gif" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H3>Second option: X410 App</H3> <P>The app is available on Microsoft Store:</P> <P><A href="#" target="_blank" rel="noopener">https://www.microsoft.com/store/productId/9NLP712ZMN9Q</A></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="X410.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/227060iAF981CB10CAB7F6F/image-size/large?v=v2&amp;px=999" role="button" title="X410.png" alt="X410.png" /></span></P> <P>&nbsp;</P> <P>In the image below I am using the <SPAN class="css-901oao css-16my406 r-1qd0xha r-ad9z0x r-bcqeeo r-qvutc0">the X Server <A title="X410" href="#" target="_self">X410</A>&nbsp;App available in the Windows 10 store, that has a better graphic performance:</SPAN></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="WinLinux.gif" style="width: 800px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/205315i3D81D050A21068C2/image-size/large?v=v2&amp;px=999" role="button" title="WinLinux.gif" alt="WinLinux.gif" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H3>Enabling Sound</H3> <P>X410 X Server App, as well as XLaunch, supports sound on Ubuntu (18;04 and 20.04). If you are using X410, please following the steps available at:</P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="nofollow noopener noreferrer">https://x410.dev/cookbook/wsl/enabling-sound-in-wsl-ubuntu-let-it-sing/</A></P> <P>&nbsp;</P> <P>I only had to change the third step to use auth-anonymous instead of the <SPAN>auth-ip-acl</SPAN>:</P> <PRE>load-module module-native-protocol-tcp auth-anonymous=1</PRE> <P>&nbsp;</P> <P>More details here:&nbsp;<SPAN><A href="#" target="_blank" rel="noopener">https://github.com/microsoft/WSL/issues/4205</A></SPAN></P> <P>&nbsp;</P> <P><SPAN>I defined the PULSE_SERVER environment variable too, as:</SPAN></P> <P>&nbsp;</P> <LI-CODE lang="basic">export PULSE_SERVER=tcp:$(grep nameserver /etc/resolv.conf | awk '{print $2}')</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Thank you&nbsp;<LI-USER uid="851344"></LI-USER>,&nbsp;for letting me know that the following <SPAN>Pulse Audio version also works:&nbsp;</SPAN><A href="#" target="_self" rel="nofollow noopener noreferrer">pulseaudio-5.0-rev 18</A><SPAN>&nbsp;.</SPAN></P> <P>&nbsp;</P> <P>This is the code bat file that I am using to load X410:</P> <P>&nbsp;</P> <LI-CODE lang="basic">@echo off start /B x410.exe /wm start "" /B "C:\wsl\pulseaudio\bin\pulseaudio.exe" ubuntu2004.exe run "if [ -z $(pidof xfce4-panel) ]; then export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2; exit;}'):0;export PULSE_SERVER=tcp:$(grep nameserver /etc/resolv.conf | awk '{print $2}'):0; cd ~; xfsettingsd --sm-client-disable; xfce4-panel --sm-client-disable --disable-wm-check; taskkill.exe /IM x410.exe;taskkill.exe /IM pulseaudio.exe /F; fi;"</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><SPAN class="css-901oao css-16my406 r-1qd0xha r-ad9z0x r-bcqeeo r-qvutc0">For more details about how to enable the <STRONG>xfce-panel </STRONG>steps, please check:</SPAN></P> <P><SPAN class="css-901oao css-16my406 r-1qd0xha r-ad9z0x r-bcqeeo r-qvutc0"><A href="#" target="_blank" rel="noopener">https://x410.dev/cookbook/wsl/xidekick/</A>&nbsp;</SPAN></P> <P>&nbsp;</P> <P>Follows a great thread about how to fix sound issues:</P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="noopener">No sound in wsl2 · Issue #4205 · microsoft/WSL (github.com)</A></P> <P>&nbsp;</P> <H3>Third option: Kali App</H3> <P>Kali Linux Windows Application is available on Microsoft Store:</P> <P><A href="#" target="_blank" rel="noopener">https://www.microsoft.com/store/productId/9PKR34TNCV07</A></P> <P>&nbsp;</P> <P>Once installed, after having providing the credentials, run the following command to install Win-KeX to allow running GUI Linux Apps on KALI for Windows:</P> <P>&nbsp;</P> <LI-CODE lang="bash">sudo apt update &amp;&amp; sudo apt install kali-win-kex</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Run the following command to run Kali:</P> <P>&nbsp;</P> <LI-CODE lang="bash">kex wstart</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="kex wstart.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/227066i0412C7F730A74025/image-size/large?v=v2&amp;px=999" role="button" title="kex wstart.png" alt="kex wstart.png" /></span></P> <P>&nbsp;</P> <P>The KALI environment will be loaded:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="kali.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/227067i5EE54DF73B779214/image-size/large?v=v2&amp;px=999" role="button" title="kali.png" alt="kali.png" /></span></P> <P>&nbsp;</P> <P>With Kali, you don't have to worry about setting environment variables or starting services.</P> <P>&nbsp;</P> <P>Follows the Kali page with more details:</P> <P><A href="#" target="_blank" rel="noopener">Kali Linux in the Windows App Store | Kali Linux</A></P> <P><A href="#" target="_blank" rel="noopener">Win-KeX Version 2.0 | Kali Linux</A></P> <P>&nbsp;</P> <P>In this post we see how to run GUI Linux Apps using <STRONG>XServer</STRONG> on Windows Desktop environment and how to access the full WSL Linux desktop environment.</P> <P>&nbsp;</P> <P>I hope you liked!</P> <P>&nbsp;</P> <P>&nbsp;</P> Fri, 15 Jan 2021 23:49:05 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/running-wsl-gui-apps-on-windows-10/ba-p/1493242 luisdem 2021-01-15T23:49:05Z Using WSL2 in a Docker Linux container on Windows to run a Minecraft Java Edition https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/using-wsl2-in-a-docker-linux-container-on-windows-to-run-a/ba-p/1482133 <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="logo.png" style="width: 886px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200170iFEA3AB92B2E7AA0A/image-size/large?v=v2&amp;px=999" role="button" title="logo.png" alt="logo.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In this post I will demonstrate how to update the <STRONG>Windows Subsystem for Linux - WSL&nbsp;</STRONG>to version 2 aka <STRONG>WSL2</STRONG>. Once updated, I will demonstrate how to <STRONG>configure Docker to use WSL2</STRONG> to run a <STRONG>Linux Minecraft Java Edition container</STRONG> natively on Windows without emulation, i.e., without a Hyper-V VM.</P> <P>&nbsp;</P> <P>Pre-requirement:</P> <P>Running Windows 10, updated to version 2004, Build 19041 or higher.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2>Virtual Machine</H2> <P>&nbsp;</P> <P>In case you want to install WSL 2 in a virtual machine, you need to turn off the VM and run the following command as admin <STRONG>on the host machine</STRONG>:</P> <P>&nbsp;</P> <LI-CODE lang="powershell">Set-VMProcessor -VMName Windows10 -ExposeVirtualizationExtensions $true</LI-CODE> <P>&nbsp;</P> <P>Notice that Windows10 is the name of my virtual machine on Hyper-V:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="hyper-v.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/206495i1243CC84694FA0D8/image-size/large?v=v2&amp;px=999" role="button" title="hyper-v.png" alt="hyper-v.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>If you have Windows 10 build 2004 or higher, you can install WSL2 through the following command and skip steps 1-4:</P> <LI-CODE lang="powershell">wsl --install</LI-CODE> <P>&nbsp;</P> <H2>1 - Enabling WSL</H2> <P>&nbsp;</P> <P>In case you don’t have the WSL installed, before installing any Linux distributions on Windows, you must enable the <STRONG>Windows Subsystem for Linux</STRONG>&nbsp;optional feature.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="enable wsl feature.png" style="width: 415px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200173iBF97AC764CB3F519/image-size/large?v=v2&amp;px=999" role="button" title="enable wsl feature.png" alt="enable wsl feature.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>You need to restart restart Windows to apply the settings.</P> <P>&nbsp;</P> <H2>Check the WSL version</H2> <P>&nbsp;</P> <P>That session is only for those that already have WSL 1 installed. If you are installing WSL from scratch, please skip this session.</P> <P>&nbsp;</P> <P>If you are using the Windows Build 19041 or higher, you can check the WSL version by opening the PowerShell command line and entering the following command:</P> <P>&nbsp;</P> <LI-CODE lang="bash">wsl --list --verbose</LI-CODE> <P>&nbsp;</P> <P>So far, I am running the version one:&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl l v.png" style="width: 655px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200179i87CB8968F22B15D9/image-size/large?v=v2&amp;px=999" role="button" title="wsl l v.png" alt="wsl l v.png" /></span></P> <P>&nbsp;</P> <P>In case you don't have a distro Linux installed, the following message will be displayed with the address to download a Linux distro on Microsoft Store:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="wsl witho distro.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/214730i9F44202967A62A1F/image-size/large?v=v2&amp;px=999" role="button" title="wsl witho distro.png" alt="wsl witho distro.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H2>2 - Enable the 'Virtual Machine Platform' optional component</H2> <P>&nbsp;</P> <P>Before installing WSL 2, you must enable the <STRONG>Virtual Machine Platform</STRONG>&nbsp;optional feature.</P> <H2>&nbsp;</H2> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="enable vmp 2.png" style="width: 415px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200193i6C5D76395814A90A/image-size/large?v=v2&amp;px=999" role="button" title="enable vmp 2.png" alt="enable vmp 2.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>If you prefer, it is possible to enable the feature using the following command:</P> <P>&nbsp;</P> <LI-CODE lang="powershell">Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform</LI-CODE> <P>&nbsp;</P> <H2>3 - Install your Linux distribution of choice</H2> <P>&nbsp;</P> <P>In case you don’t have a Linux distribution installed, you need to download install it from Microsoft Store. Follows some options available:</P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="noopener">https://aka.ms/wslstore</A></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="linux distr on store.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200175i5C966B2D10455FF5/image-size/large?v=v2&amp;px=999" role="button" title="linux distr on store.png" alt="linux distr on store.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>I am using Ubuntu 20.04 LTS:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Ubuntu.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200178iFB87772460DEB82A/image-size/large?v=v2&amp;px=999" role="button" title="Ubuntu.png" alt="Ubuntu.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Before launching the Linux distro, we need to update WSL to version 2.&nbsp;In the next session it will be demonstrated how to install WSL2.&nbsp;</P> <P>&nbsp;</P> <H2>4- Installing WSL2</H2> <P>&nbsp;</P> <P>WSL2 is now available in the initial release of Windows 10, version 2004 and Windows Insiders slow ring. But for now, to enable WSL2 you need to <STRONG>manually install</STRONG> the Linux kernel. It is a temporary solution and, in a few months, WSL2 will be automatically updated just like regular updates on your machine.</P> <P>&nbsp;</P> <P>Use the following direct link to <STRONG>download the installer for the Linux kernel update</STRONG> package:</P> <P><A href="#" target="_blank" rel="noopener">https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi</A></P> <P>&nbsp;</P> <P>The link is available at: <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/wsl/wsl2-kernel</A></P> <P>&nbsp;</P> <P>Run the update setup to install WSL2:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl2.png" style="width: 495px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200184iC48A04334B587587/image-size/large?v=v2&amp;px=999" role="button" title="wsl2.png" alt="wsl2.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>The installation process is straightforward and you only need to follows the default options. The following UI will be displayed after successfully updated:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl installed.png" style="width: 495px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200187iF0C7DC0EC75F89E7/image-size/large?v=v2&amp;px=999" role="button" title="wsl installed.png" alt="wsl installed.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H2>5 - Finish the Ubuntu installation</H2> <P>This session applies only for those that are installing Ubuntu or other Linux distro for the first time. Now that WSL 2 Kernel update is installed, you can open the Ubuntu App to complete the settings:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ubuntu first launch.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/206504iAC815E9EEDFADC79/image-size/large?v=v2&amp;px=999" role="button" title="ubuntu first launch.png" alt="ubuntu first launch.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H2>6 - Set your distribution version to WSL 2</H2> <P>Please skip this session in case you are installing WSL for the first time.&nbsp;This step is only necessary for those that already were using Ubuntu. You can now open Ubuntu to finish the setup.</P> <P>&nbsp;</P> <P>Now that you have installed WSL2, it is necessary to run the following PowerShell command to update WSL to version 2:</P> <P>&nbsp;</P> <LI-CODE lang="bash">wsl --set-version Ubuntu-20.04 2</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><STRONG>NOTE: Ubuntu-20.04</STRONG> is the name of the Linux distribution that I am using. You can run <STRONG>wsl -l -v</STRONG> to list the Linux distributions installed.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl sv 2.png" style="width: 835px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200195i9D2E96297E3373DE/image-size/large?v=v2&amp;px=999" role="button" title="wsl sv 2.png" alt="wsl sv 2.png" /></span></P> <P>&nbsp;</P> <P>Otherwise, all you have to do is to launch Ubuntu to finish the settings, like define user and password. WSL 2 will be automatically set.</P> <P>&nbsp;</P> <H2>7 - Set WSL 2 as your default version</H2> <P>Please skip this session in case you are installing WSL for the first time.&nbsp;This step is only necessary for those that already were using Ubuntu. You can now open Ubuntu to finish the setup.</P> <P>&nbsp;</P> <P>You need to run the following command in PowerShell to set WSL 2 as the default version in case you want to install others Linux distribution in the future:</P> <P>&nbsp;</P> <LI-CODE lang="bash">wsl --set-default-version 2</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl2 default version.png" style="width: 853px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200200iEB230E4A96BF7432/image-size/large?v=v2&amp;px=999" role="button" title="wsl2 default version.png" alt="wsl2 default version.png" /></span></P> <P>&nbsp;</P> <P>If you run again the following command you will see that WSL2 was successfully installed:</P> <P>&nbsp;</P> <LI-CODE lang="bash">wsl -l -v</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wsl l v 2.png" style="width: 736px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200204iC20BD695FB148D42/image-size/large?v=v2&amp;px=999" role="button" title="wsl l v 2.png" alt="wsl l v 2.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In the next session I will configure Docker to use WSL2 for Linux containers on Windows 10.</P> <P>&nbsp;</P> <H2>8 - Enable WSL2 on Docker</H2> <P>&nbsp;</P> <P>Now that we have installed <STRONG>WSL2,</STRONG> we can configure it on <STRONG>Docker Desktop for Windows</STRONG>.</P> <P>&nbsp;</P> <P><SPAN>Windows Subsystem for Linux (WSL) 2 introduces a significant architectural change as it is a full Linux kernel built by Microsoft, <STRONG>allowing Linux containers to run natively without emulation</STRONG>.&nbsp;</SPAN></P> <P>&nbsp;</P> <P>Docker Desktop uses the dynamic memory allocation feature in <STRONG>WSL 2 to greatly improve the resource consumption</STRONG>. This means, Docker Desktop only uses the required amount of CPU and memory resources it needs, while enabling CPU and memory-intensive tasks such as building a container to run much faster.</P> <P>&nbsp;</P> <P>Additionally, with WSL 2, <STRONG>the time required to start a Docker daemon after a cold start is significantly faster</STRONG>. It takes <STRONG>less than 10 seconds to start the Docker daemon</STRONG> when compared to almost a minute in the previous version of Docker Desktop.</P> <P>&nbsp;</P> <P><SPAN>More details at:</SPAN></P> <P>&nbsp;</P> <P>Docker Desktop WSL 2 backend</P> <P><SPAN><A href="#" target="_blank" rel="noopener">https://docs.docker.com/docker-for-windows/wsl/</A></SPAN></P> <P>&nbsp;</P> <P>If you are running a supported system, Docker will prompt you to enable WS2 during initialization.</P> <P>It is possible to enable WSL2 in the <STRONG>Settings</STRONG> <STRONG>&gt;</STRONG> <STRONG>General</STRONG> options:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="docker wsl2 launch.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200219iF726AA0C10C905A7/image-size/large?v=v2&amp;px=999" role="button" title="docker wsl2 launch.png" alt="docker wsl2 launch.png" /></span></P> <P>&nbsp;</P> <P>Now that we have Docker configured to run Linux containers using the WSL2 engine, we can test it with a Minecraft Linux container.</P> <P>&nbsp;</P> <H2>9 - Minecraft Linux Container</H2> <P>&nbsp;</P> <P>To demonstrate the use of Docker with WSL2, I&nbsp;will use the&nbsp;<A title="itzg/docker-minecraft-server" href="#" target="_blank" rel="noopener">itzg/docker-minecraft-server</A>&nbsp;Docker Linux image t<SPAN>hat provides a Minecraft Java Edition Server.</SPAN></P> <P>&nbsp;</P> <P><SPAN>You can use the following command to download and run the latest Minecraft Server image:</SPAN></P> <P>&nbsp;</P> <P><SPAN><!--StartFragment --></SPAN></P> <DIV><LI-CODE lang="bash">docker run -e EULA=TRUE -d -p 25565:25565 --name mc itzg/minecraft-server</LI-CODE></DIV> <P><SPAN><!--EndFragment -->&nbsp;</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="docker pull.gif" style="width: 790px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200277iF67AB6822D0410CD/image-size/large?v=v2&amp;px=999" role="button" title="docker pull.gif" alt="docker pull.gif" /></span></SPAN></P> <P>&nbsp;</P> <P>&nbsp;</P> <P><SPAN>Observe that with WSL2, the container initialization was only 10 seconds:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="docker start.gif" style="width: 948px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/200284i4245D836464557F6/image-size/large?v=v2&amp;px=999" role="button" title="docker start.gif" alt="docker start.gif" /></span></SPAN></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>I hope you liked!</P> <P>&nbsp;</P> <P>Follows some references:</P> <P>&nbsp;</P> <P>Windows Subsystem for Linux Installation Guide for Windows 10</P> <P><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/wsl/install-win10</A></P> <P>&nbsp;</P> <P>Updating the WSL 2 Linux kernel</P> <P><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/wsl/wsl2-kernel</A></P> <P>&nbsp;</P> <P>Get started with Docker for Windows</P> <P><A href="#" target="_blank" rel="noopener">https://docs.docker.com/docker-for-windows/</A></P> <P>&nbsp;</P> <P>Docker Desktop WSL 2 backend</P> <P><A href="#" target="_blank" rel="noopener">https://docs.docker.com/docker-for-windows/wsl/</A></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> Tue, 01 Dec 2020 00:20:41 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/using-wsl2-in-a-docker-linux-container-on-windows-to-run-a/ba-p/1482133 luisdem 2020-12-01T00:20:41Z Using Go language to automate the Windows Terminal launching https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/using-go-language-to-automate-the-windows-terminal-launching/ba-p/1471164 <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="logo.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199277i94A429AE02BFC133/image-size/large?v=v2&amp;px=999" role="button" title="logo.png" alt="logo.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="go.jpg" style="width: 131px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199256iE044D39B4AD73C93/image-size/large?v=v2&amp;px=999" role="button" title="go.jpg" alt="go.jpg" /></span></P> <H3>&nbsp;</H3> <H3>&nbsp;</H3> <H3>Let’s start!</H3> <P>&nbsp;</P> <P>The first step is to download and install the Go language that is available at:<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="nofollow noopener">https://golang.org/</A> .</P> <P>&nbsp;</P> <P>Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="go learning.png" style="width: 75px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199258i965CD6214771BAC1/image-size/medium?v=v2&amp;px=400" role="button" title="go learning.png" alt="go learning.png" /></span></P> <P>&nbsp;</P> <H3>&nbsp;</H3> <H3>Launching Windows Terminal from Windows Explorer Address Bar</H3> <P>&nbsp;</P> <P>I am pretty sure that you already know that typing CMD or PowerShell.exe on the Windows Explorer address bar will open the respective prompts in the same folder that it was displayed on Windows Explorer.</P> <P>&nbsp;</P> <P>But how to achieve the same behavior with Windows Terminal?</P> <P>&nbsp;</P> <P>If you type<SPAN>&nbsp;</SPAN><STRONG>wt<SPAN>&nbsp;</SPAN></STRONG>on the address bar, Windows Terminal will not be launched in the same folder that Windows Explorer. In fact, Windows Terminal will be opened in the folder defined in the default profile.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wt.gif" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199259i7222EC4609E2B27E/image-size/large?v=v2&amp;px=999" role="button" title="wt.gif" alt="wt.gif" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>:triangular_flag:</img> To open Windows Terminal in the same folder of Windows Explorer you need to run the following command:</P> <P>&nbsp;</P> <P>&nbsp;</P> <PRE spellcheck="false">wt -d . </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wt param.gif" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199260i85CDC56F8904FA48/image-size/large?v=v2&amp;px=999" role="button" title="wt param.gif" alt="wt param.gif" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In case you don't like to type the parameters for the wt command, you can use the language Go to reduce it to only a single command.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>:folded_hands:</img> I would like to thank my friend and teammate Alexandre Teoi that shared with me his implementation using Go to avoid having to pass parameters to wt command.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="git.jpg" style="width: 175px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199262iECC6FA988853A90D/image-size/medium?v=v2&amp;px=400" role="button" title="git.jpg" alt="git.jpg" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>You can find Teoi's solution on GitHub:<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="nofollow noopener">https://github.com/DevelopersCommunity/wtd .</A></P> <P>&nbsp;</P> <P>The implementation is straightforward. The<SPAN>&nbsp;</SPAN><STRONG>wtd.go<SPAN>&nbsp;</SPAN></STRONG>file, available on GitHub, is used to run the<SPAN>&nbsp;</SPAN><STRONG>wt -d .</STRONG><SPAN>&nbsp;</SPAN>command:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wtd go.png" style="width: 995px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199266iB8C77122232EB684/image-size/large?v=v2&amp;px=999" role="button" title="wtd go.png" alt="wtd go.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Once you have installed<SPAN>&nbsp;</SPAN><STRONG>Go</STRONG>, run the following code to compile and install packages:</P> <P>&nbsp;</P> <PRE spellcheck="false">go install github.com/DevelopersCommunity/wtd </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>:triangular_flag:</img> It is also possible to clone the repository and install the App locally:</P> <P>&nbsp;</P> <P>&nbsp;</P> <PRE spellcheck="false">go install wtd.go </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Now you can use wtd instead of wt to open Windows Terminal from Windows Explorer address bar without having to worry about pass the parameters.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wtd.gif" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199267i0F47F7E7261F65EA/image-size/large?v=v2&amp;px=999" role="button" title="wtd.gif" alt="wtd.gif" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="go learning.png" style="width: 75px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199258i965CD6214771BAC1/image-size/medium?v=v2&amp;px=400" role="button" title="go learning.png" alt="go learning.png" /></span></P> <P>&nbsp;</P> <H3>&nbsp;</H3> <H3>Automating Windows Terminal launching</H3> <P>&nbsp;</P> <P>The idea of this session is to demonstrate how to open Windows Terminal with three panels opened running different commands.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wt split.gif" style="width: 539px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199269i0D707E62DB5A5E40/image-size/large?v=v2&amp;px=999" role="button" title="wt split.gif" alt="wt split.gif" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P><SPAN>The first thing that you need to do is to create the following three profiles on Windows Terminal to host each panel.</SPAN></P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wt profiles.png" style="width: 977px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199270i7EF61B9C4A51B5D1/image-size/large?v=v2&amp;px=999" role="button" title="wt profiles.png" alt="wt profiles.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Follows the code that must be included in the session profiles of the<SPAN>&nbsp;</SPAN><STRONG>settings.json</STRONG><SPAN>&nbsp;</SPAN>file of Windows Terminal:</P> <P>&nbsp;</P> <P>&nbsp;</P> <PRE spellcheck="false">{ &nbsp; &nbsp; "guid": "{0879968C-03C7-4B16-BF02-49CBF6046F59}", &nbsp; &nbsp; "fontFace" : "Cascadia Code PL", &nbsp; &nbsp; "name": "Pane1", &nbsp; &nbsp; "backgroundImage": "C:\\podcast\\Windows Terminal\\windows.png", &nbsp; &nbsp; "commandline": "powershell.exe -noexit -command c:\\users\\luisdem\\pane1.ps1" }, { &nbsp; &nbsp; "guid": "{C7A929B8-F4F5-4B30-9AE4-761BD582B036}", &nbsp; &nbsp; "fontFace" : "Cascadia Code PL", &nbsp; &nbsp; "name": "Pane2", &nbsp; &nbsp; "backgroundImage": "C:\\podcast\\Windows Terminal\\linux.png", &nbsp; &nbsp; "commandline": "powershell.exe -noexit -command c:\\users\\luisdem\\pane2.ps1" }, { &nbsp; &nbsp; "guid": "{F7F214CA-48DF-4CB2-8B21-54D195867B14}", &nbsp; &nbsp; "fontFace" : "Cascadia Code PL", &nbsp; &nbsp; "name": "Pane3", &nbsp; &nbsp; "backgroundImage": "C:\\podcast\\Windows Terminal\\linux.png", &nbsp; &nbsp; "commandline": "powershell.exe -noexit -command c:\\users\\luisdem\\pane3.ps1" } </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>In my sample, I am running a PowerShell script for each profile (Pane1, Pane2 and Pane3).</P> <P>The content of those scripts are:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Pane1.ps:</P> <P>&nbsp;</P> <PRE spellcheck="false">#First part get-process #Second part [void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') [System.Windows.Forms.SendKeys]::SendWait("%{RIGHT}") [void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') [System.Windows.Forms.SendKeys]::SendWait("%+{UP}") [void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') [System.Windows.Forms.SendKeys]::SendWait("%+{UP}") [void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') [System.Windows.Forms.SendKeys]::SendWait("%+{UP}") </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>The first part (#First part) is used to list all the processes running and the second part (#Second part) is used to invoke the short keys responsible to change the panel size.</P> <P>&nbsp;</P> <P><STRONG>%{RIGHT}</STRONG>: is used to invoke the Shift+Right Arrow to change the focus to the right pane.</P> <P><STRONG>%+{UP}</STRONG>: is used to invoke the ALT+Shift+Up Arrow to change the vertical size of the pane.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Pane2.ps:</P> <P>&nbsp;</P> <PRE spellcheck="false">wsl cowsay "Welcome to Windows Terminal" wsl wsl </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Pane3.ps:</P> <P>&nbsp;</P> <PRE spellcheck="false">wsl cmatrix wsl </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Finally we can use Go to create the App that will open Windows Terminal with the three new profiles running in different panes.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>The idea is to run the following command:</P> <P>&nbsp;</P> <PRE spellcheck="false">wt -p "Pane1" `; split-pane -p "Pane2" `; split-pane -H -p "Pane3" </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>All you have to do is to create a file, like<SPAN>&nbsp;</SPAN><STRONG>wtp.go</STRONG><SPAN>&nbsp;</SPAN>and implement the following code:</P> <P>&nbsp;</P> <P>&nbsp;</P> <PRE spellcheck="false">package&nbsp;main import&nbsp;( &nbsp;&nbsp;&nbsp;&nbsp;"fmt" &nbsp;&nbsp;&nbsp;&nbsp;"os/exec" ) func&nbsp;main()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;cmd&nbsp;:=&nbsp;&amp;exec.Cmd{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Path:&nbsp;"cmd", &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Args:&nbsp;[]string{"/c",&nbsp;"start",&nbsp;"wt",&nbsp;"-p",&nbsp;"Pane1",&nbsp;"`;",&nbsp;"split-pane",&nbsp;"-p",&nbsp;"Pane2",&nbsp;"`;",&nbsp;"split-pane",&nbsp;"-H",&nbsp;"-p",&nbsp;"Pane3"}, &nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;err&nbsp;:=&nbsp;cmd.Start();&nbsp;err&nbsp;!=&nbsp;nil&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fmt.Printf("Error:&nbsp;%s\n",&nbsp;err) &nbsp;&nbsp;&nbsp;&nbsp;} } </PRE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Follows the wtp.go file opened on Visual Studio Code:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wtp vscode.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199274iF4383787E9D8E39A/image-size/large?v=v2&amp;px=999" role="button" title="wtp vscode.png" alt="wtp vscode.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Once we have the file, we need to compile and install the App through the following command:</P> <P>&nbsp;</P> <P>&nbsp;</P> <PRE spellcheck="false">go install wtp.go </PRE> <P>&nbsp;</P> <P>Now, all you have to do is to execute wtp to launch this customized Windows Terminal:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="wtp run.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/199276i10B50CF44D754C00/image-size/large?v=v2&amp;px=999" role="button" title="wtp run.png" alt="wtp run.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>This post is just to demonstrate how to use Go language to customize some actions to open Windows Terminal.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>It is possible to achieve the same behavior using other languages. :beaming_face_with_smiling_eyes:</img></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>I hope you enjoyed this post. :thumbs_up:</img></P> <P>&nbsp;</P> Wed, 17 Jun 2020 16:14:51 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/using-go-language-to-automate-the-windows-terminal-launching/ba-p/1471164 luisdem 2020-06-17T16:14:51Z FIPs and .NET Core https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/fips-and-net-core/ba-p/1454638 <P>FIPs stands for Federal Information Processing Standards. It is all about cryptographic algorithms. Chances are, if you are not working on a healthcare or government related system – you’ve never even heard of FIPs, and that’s okay.</P> <P>&nbsp;</P> <P><FONT size="5"><EM>But what about those that <STRONG><U>do</U></STRONG> have to worry about FIPs?</EM></FONT></P> <P>&nbsp;</P> <P><STRONG>Customer Scenario</STRONG><BR />This particular customer has a large healthcare system – a system that operates under mandates that <EM>requires</EM> FIPs compliance. Currently it is built on .NET Framework mostly 4.7.2. It consists of server-side NT Services that reach out to external systems to gather data along with a mix of Web Services (WCF &amp; ASMX). They are currently FIPs compliant. They are in the process of migrating these systems to .NET Core – the Web Services will be transformed into .NET Core WebAPI micro-services and the Win32 services to .NET Core. The challenge? How to maintain FIPs compliance with the migration and refactoring process taking place. Although FIPs complaint… it’s something they haven’t had to concern themselves with in a while.</P> <P>&nbsp;</P> <P><STRONG>Customer Questions</STRONG></P> <OL> <LI>What is FIPs?</LI> <LI>What is FIPs “compliance” vs “certification” vs “verification”?</LI> <LI>What is “FIPs Policy” and Best Practices?</LI> <LI>What methods are available at runtime to detect “FIPs Policy”?</LI> <LI>What methods are available to enforce FIPs Policy – at runtime?</LI> <OL> <LI>Is it possible to (accidentally) bypass FIPs Policy enforcement?</LI> </OL> <LI>What is available to <EM>audit</EM> FIPs Policy adherence?</LI> </OL> <P>&nbsp;</P> <P><FONT size="5">What is FIPs?</FONT></P> <P>An implementation of an approved cryptographic algorithm is considered FIPS compliant only if it has been submitted for and has passed National Institute of Standards and Technology (<A href="#" target="_blank">NIST</A>) validation. &nbsp;There is a program called <A href="#" target="_blank">Cryptographic Module Validation Program</A>&nbsp;(CMVP) which certifies cryptographic modules – for a full list of the modules in Windows 10 (by version) that have been certified go <A href="#" target="_blank">here</A>. A particular implementation of an algorithm that has not been submitted cannot be considered FIPS-compliant even if it produces <EM>identical data</EM> as a validated implementation of the same algorithm. Turns out, that is a very subtle but important distinction.</P> <P>&nbsp;</P> <P><FONT size="5">What is FIPs “compliance” vs “certification” vs “verification”?</FONT></P> <P>&nbsp;</P> <P>“FIPS <STRONG>Validated</STRONG>” translates to that the cryptographic module or your service or product that embeds the module has been validated (“certified”) by the CMVP as meeting the FIPS 140-2 requirements. &nbsp;Basically, FIPS Validated means that a product has been reviewed, tested, and approved by an accredited (NIST approved) <A href="#" target="_blank">testing lab</A>.&nbsp; That seems to be a very time consuming and expensive process.</P> <P>&nbsp;</P> <P>“FIPS <STRONG>Compliant</STRONG>” is an industry term for IT products that rely on FIPS 140 validated products for cryptographic functionality. In this case – the entire solution or product is not FIPS Validated, but the module it consumes are validated and the developer (and testers) have confirmed only the FIPs Validated modules are being used. Compliant does not mean a product that contains cryptographic modules is validated. Being FIPS compliant means only certain parts of a product have been tested and approved. Yes, that means there could be gaps in the security of the product.&nbsp; Being FIPs compliant is the first step before FIPs Validation by NIST, and in some cases being FIPs Complaint alone may suffice for the customer’s implementation.</P> <P>&nbsp;</P> <P>“FIPS <STRONG>Verified</STRONG>” – once an implementation has been validated by NIST, they issue a “FIPS Verified” certificate, which makes it eligible for US government procurement and use.</P> <P>&nbsp;</P> <P><FONT size="5">What is “FIPs Policy” and Best Practices?</FONT></P> <P>When it comes to Windows and development… “FIPs Policy” has a very specific meaning. It’s referring to the specific “<A href="#" target="_blank">System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing</A>” Group Policy setting located at:</P> <P>&nbsp;</P> <LI-CODE lang="bash">Computer Configuration\Windows Settings\Security Settings\Local Policies\Security Options </LI-CODE> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="policy.png" style="width: 703px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197967i54C968A78418D1CA/image-size/large?v=v2&amp;px=999" role="button" title="policy.png" alt="policy.png" /></span></P> <P>&nbsp;</P> <P>Setting this policy to Enabled will set the corresponding Registry key <STRONG>Enabled=1</STRONG>.</P> <P>&nbsp;</P> <LI-CODE lang="applescript">HKLM\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy\Enabled</LI-CODE> <P>&nbsp;</P> <P><FONT color="#FF0000"><STRONG>Caveat:</STRONG></FONT> Having this <A href="#" target="_blank">Policy</A> set to “Enabled” may indeed be required as part of your journey towards “FIPs Compliant”… however – you should be aware that there are <A href="#" target="_blank">dramatic side-effect</A> impacts of implementing this setting and in particular as you migrate towards .NET Core this setting will not give you the piece of mind you are seeking. <EM>Be warned!</EM></P> <P>&nbsp;</P> <P>There is an <A href="#" target="_blank">excellent article</A> that explains our recommended best practices and the underlying reasons for those recommendations regarding this FIPs policy.</P> <P>&nbsp;</P> <P><FONT size="5" color="#0000FF">It boils down to… <EM>unless</EM> you are required (say government or healthcare requirement)… to leave this setting <EM>undefined</EM>.</FONT></P> <P>&nbsp;</P> <P>But in this case – this customer scenario – we are indeed required to have that setting enabled. In addition – we must determine if our migrated solution is FIPs Compliant.</P> <P>&nbsp;</P> <P><FONT size="5"><STRONG>What methods are available at runtime to detect “FIPs Policy”? What methods are available to enforce FIPs Policy – at runtime? And finally, is it possible to (accidentally) bypass FIPs Policy enforcement?</STRONG></FONT></P> <P>&nbsp;</P> <P>Let’s combine these questions – as they are all very closely related once we explore.</P> <P>Okay.. so now we have an idea of what FIPs is and what FIPs Policy is… various articles advise us that we should detect (at runtime in the code) if this FIPs Policy is enabled… and if so – enforce that. Okay… then how? (Asked my frustrated customer). From our new .NET Core implementation… (1) How do we determine if that policy is even enabled? (2) How can we tell what .NET Core algorithms are using underlying FIPs Validated modules?</P> <P>This is where we journey down the rabbit hole.</P> <P>Indeed… there is a property you can check at runtime to determine if this FIPs Policy is enabled, but I do NOT recommend using this <A href="#" target="_blank">CryptoConfig.AllowOnlyFipsAlgorithms</A><SPAN>.</SPAN></P> <P>&nbsp;</P> <P>I tested it on .NET Framework 4.7.2, 4.8 and .NET Core 3.1 – and what I found was not encouraging in any of those cases.</P> <P>&nbsp;</P> <P>In order to explore the underlying implementation in each case, I stepped into the .NET Framework (and .NET Core) base class libraries. For the .NET Framework to step into the BCLs, I needed <A href="#" target="_blank">DotNet472ZDP</A> for .NET Framework 4.7.2 and <A href="#" target="_blank">DotNet48ZDP2</A> for 4.8. You can download the zip files and expand them locally. Then you need to&nbsp; uncheck “Enable Just my Code” and check “Enable .NET Source Stepping” and for Core go ahead and enable source server.</P> <P>&nbsp;</P> <P>In this case I was exploring three different implementations in three different sources – but all had the same identical CS file names (for example Sha512Managed.cs) so what I discovered is Visual Studio caches the source when the names are identical. So while I thought I was examining the BCL for 4.8 and seeing different behavior than 4.7.2 I discovered it was still pointing to 4.7.2 source although targeting 4.8. The only way I found around that was to rename the old CS lib and force VS to prompt for the new source location.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="source stepping.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197968i4065AE8A861B87BD/image-size/large?v=v2&amp;px=999" role="button" title="source stepping.png" alt="source stepping.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In .NET Framework 4.7.2 this returns the correct value but apparently this property is only set once –when the process loads. So if you have a long running process, such as a website, and this policy is updated the value will be wrong when you read it – which will lead to unexpected behavior as enforcement relies internally in the BCLs on this one property. The .NET Framework exceptions thrown – depend on this property. What that means is – you can set FIPS Policy to Enabled on a server with an NT Service and Website running – and neither will be FIPs compliant until they are restarted. Not great, but at least we have some degree of runtime errors thrown to protect against using a non-FIPs compliant algorithm.</P> <P>&nbsp;</P> <LI-CODE lang="csharp"> if (CryptoConfig.AllowOnlyFipsAlgorithms) throw new InvalidOperationException(Environment.GetResourceString("Cryptography_NonCompliantFIPSAlgorithm")); Contract.EndContractBlock(); </LI-CODE> <P>&nbsp;</P> <P>In .NET Framework 4.8 this property still exists, and the value reading behaves identical to 4.7.2. It is set once and only once when the process loads. However – the difference is the setting is completely ignored. I examined the BCL to find out why:</P> <P>&nbsp;</P> <LI-CODE lang="csharp">if (CryptoConfig.AllowOnlyFipsAlgorithms &amp;&amp; AppContextSwitches.UseLegacyFipsThrow) throw new InvalidOperationException(Environment.GetResourceString("Cryptography_NonCompliantFIPSAlgorithm")); Contract.EndContractBlock(); </LI-CODE> <P>&nbsp;</P> <P>Ah… so it is referring to a context switch <STRONG><A href="#" target="_blank">AppContextSwitches.UseLegacyFipsThrow</A></STRONG> that I had not found documented anywhere previously. If both that and the FIPs Policy are set… then and only then we get the exception. So once we set this property then we get an exception when the Policy is set to enabled:</P> <P>&nbsp;</P> <LI-CODE lang="csharp">AppContext.SetSwitch("Switch.System.Security.Cryptography.UseLegacyFipsThrow", true);</LI-CODE> <P>&nbsp;</P> <P>This was still susceptible to the same issue in 4.7.2 where if the process was still running – no error was thrown when the Policy was updated to enabled.</P> <P>&nbsp;</P> <P>Then I tested in .NET Core 3.1. Yes, the <A href="#" target="_blank">CryptoConfig.AllowOnlyFipsAlgorithms</A> property still exists in <A href="#" target="_blank">Core version</A>. &nbsp;I was not surprised to discover no exception was thrown when using non-FIPs compliant algorithms. We have <A href="#" target="_blank">documented</A> that behavior change in .NET Core we are no longer throwing exceptions “Does&nbsp;<STRONG>not</STRONG>&nbsp;enforce the use of FIPS Approved algorithms or key sizes in .NET Core apps.”. &nbsp;</P> <P>&nbsp;</P> <P>However, I was a little surprised to discover the property did not return the correct value. It seems it is never set. Therefore… the developer cannot simply read this value to determine if FIPs Policy is enabled - and throw their own exceptions.</P> <P>&nbsp;</P> <P>Instead we went with reading the registry directly and leveraging cache with expiration.</P> <P>&nbsp;</P> <LI-CODE lang="csharp"> public bool IsFipsPolicyEnabled() { RegistryKey uac = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy", true); if (uac == null) { return false; } return (bool)uac.GetValue("Enabled"); } </LI-CODE> <P>&nbsp;</P> <P>So to summarize… there are built-in methods to enforce FIPs compliant behavior at runtime in .NET 4.7.2 by default (though restart your process after a policy change). Same for 4.8 but only with the added the UseFipsLegacyThrow context switch. None in .NET Core. So is it possible to (accidentally) bypass FIPs Policy enforcement? Absolutely! Without allot of work… that will happen by default in .NET Core.</P> <P>&nbsp;</P> <P>But this leads to the next question… and a <EM><STRONG>philosophical</STRONG> </EM>question really. Perhaps there is a very good reason for this shift in behavior. Runtime errors are always worse than compile time errors. What possible benefit is there to having a runtime exception thrown for a system that is using a non-FIPs compliant algorithm? Only perhaps if that same system needed to run in both a FIPs compliant and a non-FIPs compliant environment, perhaps. Typically, instead, those issues should be discovered and fixed before release.</P> <P>&nbsp;</P> <P>Also, to make matters even more complex – FIPs compliance isn’t binary. There are cases of the FIPs compliant algorithms – within modules that are already certified – using non-fips compliant (eg SHA1) internally. So the compliance depends on the implementation / usage. In some cases it’s okay (such as for non-secret key hashing).</P> <P>&nbsp;</P> <P>So when it comes to the runtime exceptions and checking for FIPs compliance… now that <A href="#" target="_blank">excellent article</A> makes allot more sense.</P> <P>&nbsp;</P> <P>So let’s look at identifying and correcting these at compile-time instead. That leads us to…</P> <P>&nbsp;</P> <P><STRONG><FONT size="5">What is available to <EM>audit</EM> FIPs Policy adherence?</FONT> </STRONG></P> <P>I installed the <A href="#" target="_blank">Microsoft.CodeAnalysis.FxCopAnalyzers</A><A href="#" target="_blank"> 3.0</A> nuget package and set my project’s analysis options to “Entire Solution”. In particular – I was hopeful for the DoNotUseInsecureCryptographicAlgorithms check to cover for FIPs compliant checks. However, I quickly learned that check did not check for all of the non-FIPs compliant algorithms. The full list that it checks is here:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="d.png" style="width: 581px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197969i731A7D74EC03A97F/image-size/large?v=v2&amp;px=999" role="button" title="d.png" alt="d.png" /></span></P> <P>&nbsp;</P> <P>Notability missing, for example, is Sha512Managed. Okay.. let me back up a bit. Let’s review. How do we know what algorithms in .NET Core are FIPs compliant in the first place? I have not found a “blessed” list of such algorithms… probably we need to add official documentation for that. Let’s start with what we do have.</P> <P>&nbsp;</P> <P>Based on the <A href="#" target="_blank">CMVP listing</A> we know specific exports and algorithms in the BCRYPTPRIMITIVES.DLL are FIPs Validated. So based on that – I then stepped into each implementation to confirm that (1) it was calling into BCRYPTPRIMITIVES instead of a managed implementation and that (2) it was calling an algorithm that was certified. This was not easy. The same certified export was used for both FIPs approved and non-FIPs approved algorithms (eg BCryptCreateHash). And some .NET Core implementations of the same algorithm do not use the BCRYPTPRIMITIVES and therefore even though they are the same exact algorithm… they cannot be considered FIPs compliant.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197970i67830C4DD29DBABF/image-size/large?v=v2&amp;px=999" role="button" title="de.png" alt="de.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><STRONG>*This is not any official list or statement*<BR /></STRONG>As best I can tell from that exercise … these implementations are FIPs compliant:</P> <P>HMACSHA1</P> <P>MACTripleDES</P> <P>SHA512CryptoServiceProvider</P> <P>DESCryptoServiceProvider</P> <P>TripleDESCryptoServiceProvider</P> <P>DSACryptoServiceProvider</P> <P>RSACryptoServiceProvider</P> <P>&nbsp;</P> <P>And possibly SHA1CryptoServiceProvider depending on the usage. I could not locate any Roslyn Code Analysis that checked for all cryptographic uses outside that list – on a blocked-list type of approach.</P> <P>&nbsp;</P> <P>So I wrote one – <A href="#" target="_blank">https://github.com/microsoft/Windows-AppConsult-Samples-DesktopBridge/tree/master/Blog-FIPs</A></P> <P>&nbsp;</P> <P>Now, finally, I have not mentioned much about <A href="#" target="_blank">TLS</A>. Not only is the local cryptographic algorithms used important.. but the protocols and cypher suites used must also be compliant. Fortunately… the Client (and Server) SCHANNEL protocol settings in the Registry were honored and supported and blocked TSL 1.0 and 1.1 in all three of my tests (4.7.2, 4.8 and .NET Core 3.1). In addition you can specify only compliant algorithms in your CipherSuites in either the Registry (SCHANNEL\CipherSuites) or in <A href="#" target="_blank">code</A>. There is enough there to do a separate blog post… but really there is more details out there for that problem, and easier solutions.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>See also</P> <P><A href="#" target="_blank">https://github.com/dotnet/runtime/issues/26048</A><BR /><A href="#" target="_blank">https://docs.microsoft.com/en-us/dotnet/api/system.net.security.ciphersuitespolicy?view=netcore-3.1</A><BR /><A href="#" target="_blank">https://github.com/dotnet/runtime/issues/26048</A></P> <P>&nbsp;</P> Wed, 10 Jun 2020 19:12:42 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/fips-and-net-core/ba-p/1454638 Robert_Evans 2020-06-10T19:12:42Z Enable CI/CD for Windows apps with GitHub Actions and Azure Static Web Apps https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/enable-ci-cd-for-windows-apps-with-github-actions-and-azure/ba-p/1453991 <P><A href="#" target="_blank">Azure Static Web Apps</A><SPAN>&nbsp;</SPAN>is a new Azure service launched in Preview at Build 2020, which offers an app service dedicated to static websites. You could say "why is it so special"? In the end, hosting a static website is relatively simple, since there isn't any server-side component: no databases, no ASP.NET Core or PHP or Node runtime, just pure HTML, CSS and JavaScript. Azure Static Web Apps is more than just hosting for static content: thanks to its tight connection to GitHub, it supports creating automated pipelines (through GitHub Actions) which are able to automatically build and deploy full stack web apps.</P> <P>&nbsp;</P> <P>This service is a great companion for frameworks like<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Gatsby</A><SPAN>&nbsp;</SPAN>or<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Hugo</A>, which are able to generate static pages as part of the build process. Let's say that you're using Hugo to host your personal blog. Thanks to Azure Static Web App, you can just simply commit to your GitHub repository a markdown file with your latest post to trigger the execution of a GitHub Action, which will build it as a static page.</P> <P>And if you need to add a server-side component (for example, hosting a REST API), the Azure Static Web App service supports the deployment of serverless APIs based on Azure Functions.</P> <P>&nbsp;</P> <P>However, as you know, in my everyday job I'm focused on the development and the deployment of Windows desktop applications. And, as you probably know if you follow this blog or<SPAN>&nbsp;</SPAN><A href="#" target="_blank">if you have read my book about MSIX</A>, I'm a big fan of MSIX and the App Installer technology, which enables to easily have a solid CI/CD story for Windows applications. Thanks to them, in fact, we can enable features like automatic updates, critical updates, differential updates, etc. And guess what? All you need is a static website that can host your MSIX package, plus the AppInstaller file.</P> <P>&nbsp;</P> <P>I guess now you know where I'm headed to =)</img> Let's see how we can use an Azure Static Web App to host our MSIX package, which will be automatically built and deployed by a GitHub Action.</P> <P>&nbsp;</P> <H3 id="set-up-the-azure-static-web-app">Set up the Azure Static Web App</H3> <P>As first step, let's create our Azure Static Web App. Login to the<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Azure portal</A><SPAN>&nbsp;</SPAN>with your account and click on<SPAN>&nbsp;</SPAN><STRONG>Create a resource</STRONG>. Start a search using the<SPAN>&nbsp;</SPAN><STRONG>static</STRONG><SPAN>&nbsp;</SPAN>keyword. One of the results will be<SPAN>&nbsp;</SPAN><STRONG>Static Web App (Preview)</STRONG>, as in the following image:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NewStaticWebApp.png" style="width: 896px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197910i54A5339C2D163889/image-size/large?v=v2&amp;px=999" role="button" title="NewStaticWebApp.png" alt="NewStaticWebApp.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Click on it and choose&nbsp;</SPAN><STRONG>Create</STRONG><SPAN>&nbsp;to start the process:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="CreateNewApp.png" style="width: 793px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197911i38747EFF82490AD6/image-size/large?v=v2&amp;px=999" role="button" title="CreateNewApp.png" alt="CreateNewApp.png" /></span></SPAN></P> <P>&nbsp;</P> <P><SPAN>The first information you have to provide are the subscription, the resource group, the name of the app and the region. The only available SKU, for the moment, is the Free one, since the service is in preview. Currently this service works only with GitHub, since it doesn't use it just to connect to the repository, but it's going to create the GitHub Action needed to compile and deploy the project for us. As such, the next required step is to click on&nbsp;<STRONG>Sign in with GitHub</STRONG>&nbsp;to complete the login process with your GitHub account. Once you are logged in, you will have the opportunity to choose an organization, a repository and a branch from the ones have on GitHub:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GitHubSetup.png" style="width: 772px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197912i671512D98ADD3397/image-size/large?v=v2&amp;px=999" role="button" title="GitHubSetup.png" alt="GitHubSetup.png" /></span></SPAN></P> <P>&nbsp;</P> <P>For our scenario we're going to choose a repository which contains a .NET Desktop application packaged with the Windows Application Packaging Project. The one I'm using for this blog post is available at<SPAN>&nbsp;</SPAN><A href="#" target="_blank">https://github.com/qmatteoq/ContosoApp</A>. It's just a plain WPF application based on .NET Core 3.1.</P> <P>Once you have completed the configuration, press<SPAN>&nbsp;</SPAN><STRONG>Review + create</STRONG>, followed by<SPAN>&nbsp;</SPAN><STRONG>Create</STRONG><SPAN>&nbsp;</SPAN>in the review page. Once the deployment has been completed, you will notice a few changes in your GitHub repository:</P> <P>&nbsp;</P> <UL> <LI> <P>There will be a new folder, called<SPAN>&nbsp;</SPAN><CODE>github/workflows</CODE><SPAN>&nbsp;</SPAN>with a YAML file inside. That's our GitHub workflow. We can see it also by clicking on the<SPAN>&nbsp;</SPAN><STRONG>Actions</STRONG><SPAN>&nbsp;</SPAN>tab in the repository:</P> </LI> </UL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GitHubAction.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197913i0E52C884CD0E33C9/image-size/large?v=v2&amp;px=999" role="button" title="GitHubAction.png" alt="GitHubAction.png" /></span></P> <P>The workflow is called<SPAN>&nbsp;</SPAN><STRONG>Azure Static Web Apps CI/CD</STRONG><SPAN>&nbsp;</SPAN>and it will be executed immediately. However, it will fail, since our repository contains a desktop application and the default workflow isn't tailored for this scenario. Don't worry, we're going to fix that!</P> <P>&nbsp;</P> <UL> <LI> <P>If you go to the<SPAN>&nbsp;</SPAN><STRONG>Settings</STRONG><SPAN>&nbsp;</SPAN>section of the repository and you move to the<SPAN>&nbsp;</SPAN><STRONG>Secrets</STRONG><SPAN>&nbsp;</SPAN>tab, you will find out that Azure Static Web App has added a new secret for you:</P> </LI> </UL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Secret.png" style="width: 759px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197914i584E2D6A7214E34B/image-size/large?v=v2&amp;px=999" role="button" title="Secret.png" alt="Secret.png" /></span></P> <P><SPAN>This secret contains the API token which is required by the GitHub Action to connect to the Azure Static Web App instance we have created, in order to perform the deployment.</SPAN></P> <P>&nbsp;</P> <H3 id="customize-the-workflow">Customize the workflow</H3> <P>Let's take a look at the workflow that the Azure Static Web App has created for us:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">name:</SPAN> <SPAN class="hljs-string">Azure</SPAN> <SPAN class="hljs-string">Static</SPAN> <SPAN class="hljs-string">Web</SPAN> <SPAN class="hljs-string">Apps</SPAN> <SPAN class="hljs-string">CI/CD</SPAN> <SPAN class="hljs-attr">on:</SPAN> <SPAN class="hljs-attr"> push:</SPAN> <SPAN class="hljs-attr"> branches:</SPAN> <SPAN class="hljs-bullet"> -</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-attr"> pull_request:</SPAN> <SPAN class="hljs-attr"> types:</SPAN> <SPAN class="hljs-string">[opened,</SPAN> <SPAN class="hljs-string">synchronize,</SPAN> <SPAN class="hljs-string">reopened,</SPAN> <SPAN class="hljs-string">closed]</SPAN> <SPAN class="hljs-attr"> branches:</SPAN> <SPAN class="hljs-bullet"> -</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-attr">jobs:</SPAN> <SPAN class="hljs-attr"> build_and_deploy_job:</SPAN> <SPAN class="hljs-attr"> if:</SPAN> <SPAN class="hljs-string">github.event_name</SPAN> <SPAN class="hljs-string">==</SPAN> <SPAN class="hljs-string">'push'</SPAN> <SPAN class="hljs-string">||</SPAN> <SPAN class="hljs-string">(github.event_name</SPAN> <SPAN class="hljs-string">==</SPAN> <SPAN class="hljs-string">'pull_request'</SPAN> <SPAN class="hljs-string">&amp;&amp;</SPAN> <SPAN class="hljs-string">github.event.action</SPAN> <SPAN class="hljs-string">!=</SPAN> <SPAN class="hljs-string">'closed'</SPAN><SPAN class="hljs-string">)</SPAN> <SPAN class="hljs-attr"> runs-on:</SPAN> <SPAN class="hljs-string">ubuntu-latest</SPAN> <SPAN class="hljs-attr"> name:</SPAN> <SPAN class="hljs-string">Build</SPAN> <SPAN class="hljs-string">and</SPAN> <SPAN class="hljs-string">Deploy</SPAN> <SPAN class="hljs-string">Job</SPAN> <SPAN class="hljs-attr"> steps:</SPAN> <SPAN class="hljs-attr"> - uses:</SPAN> <SPAN class="hljs-string">actions/checkout@v2</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> submodules:</SPAN> <SPAN class="hljs-literal">true</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Build</SPAN> <SPAN class="hljs-string">And</SPAN> <SPAN class="hljs-string">Deploy</SPAN> <SPAN class="hljs-attr"> id:</SPAN> <SPAN class="hljs-string">builddeploy</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">Azure/static-web-apps-deploy@v0.0.1-preview</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> azure_static_web_apps_api_token:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_CALM_DESERT_05F99C503</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> repo_token:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">secrets.GITHUB_TOKEN</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-comment"># Used for Github integrations (i.e. PR comments)</SPAN> <SPAN class="hljs-attr"> action:</SPAN> <SPAN class="hljs-string">"upload"</SPAN> <SPAN class="hljs-comment">###### Repository/Build Configurations - These values can be configured to match you app requirements. ######</SPAN> <SPAN class="hljs-comment"># For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig</SPAN> <SPAN class="hljs-attr"> app_location:</SPAN> <SPAN class="hljs-string">"/"</SPAN> <SPAN class="hljs-comment"># App source code path</SPAN> <SPAN class="hljs-attr"> api_location:</SPAN> <SPAN class="hljs-string">"api"</SPAN> <SPAN class="hljs-comment"># Api source code path - optional</SPAN> <SPAN class="hljs-attr"> app_artifact_location:</SPAN> <SPAN class="hljs-string">""</SPAN> <SPAN class="hljs-comment"># Built app content directory - optional</SPAN> <SPAN class="hljs-comment">###### End of Repository/Build Configurations ######</SPAN> <SPAN class="hljs-attr"> close_pull_request_job:</SPAN> <SPAN class="hljs-attr"> if:</SPAN> <SPAN class="hljs-string">github.event_name</SPAN> <SPAN class="hljs-string">==</SPAN> <SPAN class="hljs-string">'pull_request'</SPAN> <SPAN class="hljs-string">&amp;&amp;</SPAN> <SPAN class="hljs-string">github.event.action</SPAN> <SPAN class="hljs-string">==</SPAN> <SPAN class="hljs-string">'closed'</SPAN> <SPAN class="hljs-attr"> runs-on:</SPAN> <SPAN class="hljs-string">ubuntu-latest</SPAN> <SPAN class="hljs-attr"> name:</SPAN> <SPAN class="hljs-string">Close</SPAN> <SPAN class="hljs-string">Pull</SPAN> <SPAN class="hljs-string">Request</SPAN> <SPAN class="hljs-string">Job</SPAN> <SPAN class="hljs-attr"> steps:</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Close</SPAN> <SPAN class="hljs-string">Pull</SPAN> <SPAN class="hljs-string">Request</SPAN> <SPAN class="hljs-attr"> id:</SPAN> <SPAN class="hljs-string">closepullrequest</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">Azure/static-web-apps-deploy@v0.0.1-preview</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> azure_static_web_apps_api_token:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_CALM_DESERT_05F99C503</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> action:</SPAN> <SPAN class="hljs-string">"close"</SPAN> </CODE></PRE> <P>The workflow contains two distinct jobs:</P> <UL> <LI><STRONG>build_and_deploy_job</STRONG><SPAN>&nbsp;</SPAN>is triggered whenever you submit a new Pull Request to the repository. The goal of this job is to build the updated website included in the Pull Request and to deploy it in a staging environment, so that you can test that everything is working as expected.</LI> <LI><STRONG>close_pull_request_job</STRONG><SPAN>&nbsp;</SPAN>is triggered whenever you close a Pull Request. This means that the you have validated that the Pull Request is good and, as such, the staging environment can be deleted because the new version of the website can be safely pushed to production.</LI> </UL> <P>As you can imagine, however, for our scenario this isn't really applicable, since we aren't really deploying a website, but a desktop application to be deployed. As such, we're going to basically delete everything from this workflow file, except for the<SPAN>&nbsp;</SPAN><STRONG>Build and deploy</STRONG><SPAN>&nbsp;</SPAN>task, which is based on the action called<SPAN>&nbsp;</SPAN><STRONG>Azure/static-web-apps-deploy@v0.0.1-preview</STRONG>. We're going to use this one to publish the output of the Visual Studio compilation: the MSIX package, the AppInstaller file and the web page to trigger the installation.</P> <P>&nbsp;</P> <P>To customize the workflow, we're going to take inspiration<SPAN>&nbsp;</SPAN><A href="#" target="_blank">from the template created by .NET Team</A>, which is a great starting point when you want to build desktop .NET Core applications on GitHub. This template perfectly fits my scenario: I have a WPF application based on .NET Core and a Windows Application Packaging Project to generate the MSIX package.</P> <P>&nbsp;</P> <P>Let's start by creating a copy of the existing workflow file. We're going to need it later. Now open on GitHub the workflow file (in my case, it's<SPAN>&nbsp;</SPAN><A href="#" target="_blank">https://github.com/qmatteoq/ContosoApp/blob/master/.github/workflows/azure-static-web-apps-calm-desert-05f99c503.yml</A>) and click on the edit icon (the small pencil in the toolbar):</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="EditFile.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197915i2DA6FA4BC78F726E/image-size/large?v=v2&amp;px=999" role="button" title="EditFile.png" alt="EditFile.png" /></span></P> <P>&nbsp;</P> <P><SPAN>When you do this, GitHub will propose you a special edit interface tailored for workflows. Thanks a to a panel on the right, you'll be able to easily browse the Actions marketplace and integrate tasks in the workflow.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="EditGitHubAction.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197916i0D83E64DB78A3BB0/image-size/large?v=v2&amp;px=999" role="button" title="EditGitHubAction.png" alt="EditGitHubAction.png" /></span></SPAN></P> <P>&nbsp;</P> <P>For the moment, let's delete everything (except for the first line which defines the<SPAN>&nbsp;</SPAN><CODE>name</CODE>) and replace the content with the one<SPAN>&nbsp;</SPAN><A href="#" target="_blank">from the .NET Desktop workflow</A>. This is how your workflow should look like:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">name:</SPAN> <SPAN class="hljs-string">Azure</SPAN> <SPAN class="hljs-string">Static</SPAN> <SPAN class="hljs-string">Web</SPAN> <SPAN class="hljs-string">Apps</SPAN> <SPAN class="hljs-string">CI/CD</SPAN> <SPAN class="hljs-attr">on:</SPAN> <SPAN class="hljs-attr"> push:</SPAN> <SPAN class="hljs-attr"> branches:</SPAN> <SPAN class="hljs-string">[</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-string">]</SPAN> <SPAN class="hljs-attr"> pull_request:</SPAN> <SPAN class="hljs-attr"> branches:</SPAN> <SPAN class="hljs-string">[</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-string">]</SPAN> <SPAN class="hljs-attr">jobs:</SPAN> <SPAN class="hljs-attr"> build:</SPAN> <SPAN class="hljs-attr"> strategy:</SPAN> <SPAN class="hljs-attr"> matrix:</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">[Debug,</SPAN> <SPAN class="hljs-string">Release]</SPAN> <SPAN class="hljs-attr"> runs-on:</SPAN> <SPAN class="hljs-string">windows-latest</SPAN> <SPAN class="hljs-comment"># For a list of available runner types, refer to </SPAN> <SPAN class="hljs-comment"># https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on</SPAN> <SPAN class="hljs-attr"> env:</SPAN> <SPAN class="hljs-attr"> Solution_Name:</SPAN> <SPAN class="hljs-string">your-solution-name</SPAN> <SPAN class="hljs-comment"># Replace with your solution name, i.e. MyWpfApp.sln.</SPAN> <SPAN class="hljs-attr"> Test_Project_Path:</SPAN> <SPAN class="hljs-string">your-test-project-path</SPAN> <SPAN class="hljs-comment"># Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.</SPAN> <SPAN class="hljs-attr"> Wap_Project_Directory:</SPAN> <SPAN class="hljs-string">your-wap-project-directory-name</SPAN> <SPAN class="hljs-comment"># Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package.</SPAN> <SPAN class="hljs-attr"> Wap_Project_Path:</SPAN> <SPAN class="hljs-string">your-wap-project-path</SPAN> <SPAN class="hljs-comment"># Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj.</SPAN> <SPAN class="hljs-attr"> steps:</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Checkout</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">actions/checkout@v2</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> fetch-depth:</SPAN> <SPAN class="hljs-number">0</SPAN> <SPAN class="hljs-comment"># Install the .NET Core workload</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Install</SPAN> <SPAN class="hljs-string">.NET</SPAN> <SPAN class="hljs-string">Core</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">actions/setup-dotnet@v1</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> dotnet-version:</SPAN> <SPAN class="hljs-number">3.1</SPAN><SPAN class="hljs-number">.101</SPAN> <SPAN class="hljs-comment"># Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Setup</SPAN> <SPAN class="hljs-string">MSBuild.exe</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">microsoft/setup-msbuild@2008f912f56e61277eefaac6d1888b750582aa16</SPAN> <SPAN class="hljs-comment"># Execute all unit tests in the solution</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Execute</SPAN> <SPAN class="hljs-string">unit</SPAN> <SPAN class="hljs-string">tests</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">dotnet</SPAN> <SPAN class="hljs-string">test</SPAN> <SPAN class="hljs-comment"># Restore the application to populate the obj folder with RuntimeIdentifiers</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Restore</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">application</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">msbuild</SPAN> <SPAN class="hljs-string">$env:Solution_Name</SPAN> <SPAN class="hljs-string">/t:Restore</SPAN> <SPAN class="hljs-string">/p:Configuration=$env:Configuration</SPAN> <SPAN class="hljs-attr"> env:</SPAN> <SPAN class="hljs-attr"> Configuration:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">matrix.configuration</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-comment"># Decode the base 64 encoded pfx and save the Signing_Certificate</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Decode</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">pfx</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">| $pfx_cert_byte = [System.Convert]::FromBase64String("$<SPAN class="hljs-template-variable">{{ secrets.Base64_Encoded_Pfx }}</SPAN>") $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) # Create the app package by building and packaging the Windows Application Packaging project </SPAN><SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Create</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">app</SPAN> <SPAN class="hljs-string">package</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">msbuild</SPAN> <SPAN class="hljs-string">$env:Wap_Project_Path</SPAN> <SPAN class="hljs-string">/p:Configuration=$env:Configuration</SPAN> <SPAN class="hljs-string">/p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode</SPAN> <SPAN class="hljs-string">/p:AppxBundle=$env:Appx_Bundle</SPAN> <SPAN class="hljs-string">/p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx</SPAN> <SPAN class="hljs-string">/p:PackageCertificatePassword=${{</SPAN> <SPAN class="hljs-string">secrets.Pfx_Key</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> env:</SPAN> <SPAN class="hljs-attr"> Appx_Bundle:</SPAN> <SPAN class="hljs-string">Always</SPAN> <SPAN class="hljs-attr"> Appx_Bundle_Platforms:</SPAN> <SPAN class="hljs-string">x86|x64</SPAN> <SPAN class="hljs-attr"> Appx_Package_Build_Mode:</SPAN> <SPAN class="hljs-string">StoreUpload</SPAN> <SPAN class="hljs-attr"> Configuration:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">matrix.configuration</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-comment"># Remove the pfx</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Remove</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">pfx</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">Remove-Item</SPAN> <SPAN class="hljs-bullet">-path</SPAN> <SPAN class="hljs-string">$env:Wap_Project_Directory\$env:Signing_Certificate</SPAN> <SPAN class="hljs-comment"># Upload the MSIX package: https://github.com/marketplace/actions/upload-artifact</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Upload</SPAN> <SPAN class="hljs-string">build</SPAN> <SPAN class="hljs-string">artifacts</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">actions/upload-artifact@v1</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> name:</SPAN> <SPAN class="hljs-string">MSIX</SPAN> <SPAN class="hljs-string">Package</SPAN> <SPAN class="hljs-attr"> path:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">env.Wap_Project_Directory</SPAN> <SPAN class="hljs-string">}}\AppPackages</SPAN> </CODE></PRE> <P>Let's go and customize a few things. First, you'll need to customize the environment variables defined at the top to point to your solutions and project. You'll have to setup:</P> <P>&nbsp;</P> <UL> <LI><CODE>Solution_Name</CODE><SPAN>&nbsp;</SPAN>with the relative path of the solution file</LI> <LI><CODE>Wap_Project_Directory</CODE><SPAN>&nbsp;</SPAN>with the relative path of the folder which contains the Windows Application Packaging Project</LI> <LI><CODE>Wap_Project_Path</CODE><SPAN>&nbsp;</SPAN>with the full path of Windows Application Packaging Project file</LI> </UL> <P>Optionally, you can also use the<SPAN>&nbsp;</SPAN><CODE>Test_Project_Path</CODE><SPAN>&nbsp;</SPAN>to configure the path of the project which contains your unit tests. In my case, since it's a sample project, I don't have one, so I simply removed that variable. This is how my environment configuration looks like:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">env:</SPAN> <SPAN class="hljs-attr"> Solution_Name:</SPAN> <SPAN class="hljs-string">ContosoApp.sln</SPAN> <SPAN class="hljs-attr"> Wap_Project_Directory:</SPAN> <SPAN class="hljs-string">ContosoApp.Package</SPAN> <SPAN class="hljs-attr"> Wap_Project_Path:</SPAN> <SPAN class="hljs-string">ContosoApp.Package\ContosoApp.Package.wapproj</SPAN> </CODE></PRE> <P>Second, for our scenario, we don't really need to build the application both in Debug and Release mode. We just want to have, as output, a single release MSIX package, so that we can deploy it on our website. As such, remove from the configuration matrix the<SPAN>&nbsp;</SPAN><CODE>Debug</CODE><SPAN>&nbsp;</SPAN>entry:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">strategy:</SPAN> <SPAN class="hljs-attr"> matrix:</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">[Release]</SPAN> </CODE></PRE> <P>As next step, I'm going to customize some of the available tasks:</P> <UL> <LI> <P><CODE>Execute unit tests</CODE><SPAN>&nbsp;</SPAN>isn't needed for my sample application, since I don't have unit tests. As such, I'm going to delete this one.</P> </LI> <LI> <P><CODE>Build</CODE>. Since we're going to use the AppInstaller technology to distribute the package using our Azure Static Web App, we need to tweak a bit the various parameters.</P> <UL> <LI>The first one is the<SPAN>&nbsp;</SPAN><CODE>Appx_Package_Build_Mode</CODE><SPAN>&nbsp;</SPAN>parameter, which by default is set to<SPAN>&nbsp;</SPAN><CODE>StoreUpload</CODE>, which is the needed when you want to publish the application on the Microsoft Store. In this case we're using manual distribution, so we need to change it to<SPAN>&nbsp;</SPAN><CODE>SideloadOnly</CODE>.</LI> <LI>We need to add the<SPAN>&nbsp;</SPAN><CODE>/p:GenerateAppInstallerFile</CODE><SPAN>&nbsp;</SPAN>parameter and set it to<SPAN>&nbsp;</SPAN><CODE>true</CODE>, in order to generate the .appinstaller file and the web page as part of the process.</LI> <LI>We need to add the<SPAN>&nbsp;</SPAN><CODE>/p:AppInstallerUri</CODE><SPAN>&nbsp;</SPAN>parameter and set it to the URL that has been assigned to our Azure Static Web App. You can find this URL in the Azure portal:</LI> </UL> </LI> </UL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AzureAppUrl.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197917i01E0CD8BEE56938A/image-size/large?v=v2&amp;px=999" role="button" title="AzureAppUrl.png" alt="AzureAppUrl.png" /></span></P> <P>&nbsp;</P> <P>This is how the task looks like after the changes:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-comment"># Create the app package by building and packaging the Windows Application Packaging project</SPAN> <SPAN class="hljs-attr">- name:</SPAN> <SPAN class="hljs-string">Create</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">app</SPAN> <SPAN class="hljs-string">package</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">msbuild</SPAN> <SPAN class="hljs-string">$env:Wap_Project_Path</SPAN> <SPAN class="hljs-string">/p:Configuration=$env:Configuration</SPAN> <SPAN class="hljs-string">/p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode</SPAN> <SPAN class="hljs-string">/p:AppxBundle=$env:Appx_Bundle</SPAN> <SPAN class="hljs-string">/p:GenerateAppInstallerFile=$env:Generate_AppInstaller</SPAN> <SPAN class="hljs-string">/p:AppInstallerUri=$env:AppInstaller_Url</SPAN> <SPAN class="hljs-string">/p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx</SPAN> <SPAN class="hljs-string">/p:PackageCertificatePassword=${{</SPAN> <SPAN class="hljs-string">secrets.Pfx_Key</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> env:</SPAN> <SPAN class="hljs-attr"> Appx_Bundle:</SPAN> <SPAN class="hljs-string">Always</SPAN> <SPAN class="hljs-attr"> Appx_Bundle_Platforms:</SPAN> <SPAN class="hljs-string">x86|x64</SPAN> <SPAN class="hljs-attr"> Appx_Package_Build_Mode:</SPAN> <SPAN class="hljs-string">SideloadOnly</SPAN> <SPAN class="hljs-attr"> Configuration:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">matrix.configuration</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> Generate_AppInstaller:</SPAN> <SPAN class="hljs-literal">true</SPAN> <SPAN class="hljs-attr"> AppInstaller_Url:</SPAN> <SPAN class="hljs-attr">https://calm-desert-05f99c503.azurestaticapps.net</SPAN></CODE></PRE> <H3 id="setting-up-the-signing">Setting up the signing</H3> <P>As I have discussed multiple times in this blog, signing is a critical aspect of MSIX packaging. If you don't sign a MSIX package with a trusted certificate, the user will never be able to install it. At the same time, you must be very careful in how you enable signing as part of a CI/CD pipeline. If you expose your private certificate, someone might be able to steal it and use your identity to sign malicious applications. There are multiple approaches to sign MSIX packages in the right way as part of a CI/CD pipeline. The approach used by the workflow template is to ask to the developer to encode the PFX into a base64 string and store it as a secret. Then the workflow includes a PowerShell script which takes care of decoding the secret back into a PFX, so that it can be used as part of the Visual Studio build to do the signing.</P> <P>&nbsp;</P> <P>The main reasoning of using this approach on GitHub is that, unlike Azure DevOps, the platform doesn't have the concept of<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Secure Files</A>, which is often used in scenarios like this one.</P> <P>The GitHub Action workflow already contains everything you need to sign the package. The only missing step is to create the secrets to store the various required information.</P> <P>&nbsp;</P> <OL> <LI> <P>Open a PowerShell terminal in the folder in which you keep your PFX certificate.</P> </LI> <LI> <P>Run the following script:</P> <PRE><CODE class="language-powershell hljs"><SPAN class="hljs-variable">$pfx_cert</SPAN> = <SPAN class="hljs-built_in">Get-Content</SPAN> <SPAN class="hljs-string">'.\SigningCertificate.pfx'</SPAN> -Encoding Byte [System.Convert]::ToBase64String(<SPAN class="hljs-variable">$pfx_cert</SPAN>) | <SPAN class="hljs-built_in">Out-File</SPAN> <SPAN class="hljs-string">'SigningCertificate_Encoded.txt'</SPAN> </CODE></PRE> </LI> <LI> <P>At the end of the process you will find, in the same folder, a text file called<SPAN>&nbsp;</SPAN><STRONG>SigningCertificate_Encoded.txt</STRONG>, which contains the certificate encoded as a base64 string. Open the text file and copy the whole content.</P> </LI> <LI> <P>Now go to your repository on GitHub, choose<SPAN>&nbsp;</SPAN><STRONG>Settings → Secrets</STRONG><SPAN>&nbsp;</SPAN>and create a new secret.</P> </LI> <LI> <P>Call the secret<SPAN>&nbsp;</SPAN><CODE>Base64_Encoded_Pfx</CODE><SPAN>&nbsp;</SPAN>and, as value, paste the encoded base64 string you have just copied.</P> </LI> <LI> <P>Now create a new secret and call it<SPAN>&nbsp;</SPAN><CODE>Pfx_Key</CODE>. As value, type the password for the signing certificate.</P> </LI> </OL> <P>That's it. Another and safer alternative to this approach is to leverage Azure Key Vault to store your certificate and the Azure SignTool utility to do the actual signing as part of the pipeline. You can find all the details about this approach, including how to use it in a GitHub Action workflow,<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/signing-a-msix-package-with-azure-key-vault/ba-p/1436154" target="_blank">in a recent blog post I wrote</A>.</P> <P>&nbsp;</P> <H3 id="reviewing-the-pipeline">Reviewing the pipeline</H3> <P>We have finished the work on the build pipeline. This is how it should look like after the changes:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">name:</SPAN> <SPAN class="hljs-string">Azure</SPAN> <SPAN class="hljs-string">Static</SPAN> <SPAN class="hljs-string">Web</SPAN> <SPAN class="hljs-string">Apps</SPAN> <SPAN class="hljs-string">CI/CD</SPAN> <SPAN class="hljs-attr">on:</SPAN> <SPAN class="hljs-attr"> push:</SPAN> <SPAN class="hljs-attr"> branches:</SPAN> <SPAN class="hljs-string">[</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-string">]</SPAN> <SPAN class="hljs-attr"> pull_request:</SPAN> <SPAN class="hljs-attr"> branches:</SPAN> <SPAN class="hljs-string">[</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-string">]</SPAN> <SPAN class="hljs-attr">jobs:</SPAN> <SPAN class="hljs-attr"> build:</SPAN> <SPAN class="hljs-attr"> strategy:</SPAN> <SPAN class="hljs-attr"> matrix:</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">[Release]</SPAN> <SPAN class="hljs-attr"> runs-on:</SPAN> <SPAN class="hljs-string">windows-latest</SPAN> <SPAN class="hljs-comment"># For a list of available runner types, refer to </SPAN> <SPAN class="hljs-comment"># https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on</SPAN> <SPAN class="hljs-attr"> env:</SPAN> <SPAN class="hljs-attr"> Solution_Name:</SPAN> <SPAN class="hljs-string">ContosoApp.sln</SPAN> <SPAN class="hljs-attr"> Wap_Project_Directory:</SPAN> <SPAN class="hljs-string">ContosoApp.Package</SPAN> <SPAN class="hljs-attr"> Wap_Project_Path:</SPAN> <SPAN class="hljs-string">ContosoApp.Package\ContosoApp.Package.wapproj</SPAN> <SPAN class="hljs-attr"> steps:</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Checkout</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">actions/checkout@v2</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> fetch-depth:</SPAN> <SPAN class="hljs-number">0</SPAN> <SPAN class="hljs-comment"># Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Setup</SPAN> <SPAN class="hljs-string">MSBuild.exe</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">microsoft/setup-msbuild@2008f912f56e61277eefaac6d1888b750582aa16</SPAN> <SPAN class="hljs-comment"># Restore the application to populate the obj folder with RuntimeIdentifiers</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Restore</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">application</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">msbuild</SPAN> <SPAN class="hljs-string">$env:Solution_Name</SPAN> <SPAN class="hljs-string">/t:Restore</SPAN> <SPAN class="hljs-string">/p:Configuration=$env:Configuration</SPAN> <SPAN class="hljs-attr"> env:</SPAN> <SPAN class="hljs-attr"> Configuration:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">matrix.configuration</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-comment"># Decode the base 64 encoded pfx and save the Signing_Certificate</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Decode</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">pfx</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">| $pfx_cert_byte = [System.Convert]::FromBase64String("$<SPAN class="hljs-template-variable">{{ secrets.Base64_Encoded_Pfx }}</SPAN>") $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) # Create the app package by building and packaging the Windows Application Packaging project </SPAN><SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Create</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">app</SPAN> <SPAN class="hljs-string">package</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">msbuild</SPAN> <SPAN class="hljs-string">$env:Wap_Project_Path</SPAN> <SPAN class="hljs-string">/p:Configuration=$env:Configuration</SPAN> <SPAN class="hljs-string">/p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode</SPAN> <SPAN class="hljs-string">/p:AppxBundle=$env:Appx_Bundle</SPAN> <SPAN class="hljs-string">/p:GenerateAppInstallerFile=$env:Generate_AppInstaller</SPAN> <SPAN class="hljs-string">/p:AppInstallerUri=$env:AppInstaller_Url</SPAN> <SPAN class="hljs-string">/p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx</SPAN> <SPAN class="hljs-string">/p:PackageCertificatePassword=${{</SPAN> <SPAN class="hljs-string">secrets.Pfx_Key</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> env:</SPAN> <SPAN class="hljs-attr"> Appx_Bundle:</SPAN> <SPAN class="hljs-string">Always</SPAN> <SPAN class="hljs-attr"> Appx_Bundle_Platforms:</SPAN> <SPAN class="hljs-string">x86|x64</SPAN> <SPAN class="hljs-attr"> Appx_Package_Build_Mode:</SPAN> <SPAN class="hljs-string">SideloadOnly</SPAN> <SPAN class="hljs-attr"> Configuration:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">matrix.configuration</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> Generate_AppInstaller:</SPAN> <SPAN class="hljs-literal">true</SPAN> <SPAN class="hljs-attr"> AppInstaller_Url:</SPAN> <SPAN class="hljs-attr">https://calm-desert-05f99c503.azurestaticapps.net</SPAN> <SPAN class="hljs-comment"># Remove the pfx</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Remove</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">pfx</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">Remove-Item</SPAN> <SPAN class="hljs-bullet">-path</SPAN> <SPAN class="hljs-string">$env:Wap_Project_Directory\GitHubActionsWorkflow.pfx</SPAN> <SPAN class="hljs-comment"># Upload the MSIX package: https://github.com/marketplace/actions/upload-artifact</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Upload</SPAN> <SPAN class="hljs-string">build</SPAN> <SPAN class="hljs-string">artifacts</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">actions/upload-artifact@v1</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> name:</SPAN> <SPAN class="hljs-string">MSIX</SPAN> <SPAN class="hljs-string">Package</SPAN> <SPAN class="hljs-attr"> path:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">env.Wap_Project_Directory</SPAN> <SPAN class="hljs-string">}}\AppPackages</SPAN> </CODE></PRE> <P>This is enough to get a ready to be deployed MSIX package. If you commit any change to the repository which hosts your desktop app, the process should complete without errors and, in the end, you should have an artifact folder which contains:</P> <P>&nbsp;</P> <UL> <LI>Your signed MSIX package</LI> <LI>The file with .appinstaller extension required to install the package from a website or network share</LI> <LI>A web page (index.html) which contains the link to trigger the installation of the MSIX package</LI> </UL> <H3 id="deploy-on-azure-static-web-app">Deploy on Azure Static Web App</H3> <P>Now it's time to go back to the original workflow file that was created when we have created the Azure Static Web App and that we have copied before starting making changes. We need, in fact, to leverage the task that will take care of deploying on the Web App the artifact we have just created. However, in order to do this, we're going to create a different job. Its purpose will be to download the artifact we have generated and to deploy it.</P> <P>&nbsp;</P> <P>Why using a separate job? The first reason is more "philosophical". The deployment is a different step compared to the build process. Azure DevOps does a great job in helping to keep the two phases separated, by providing release pipelines to handle the release management story. GitHub doesn't support this approach but, still, a multi-stage pipeline can help us to better split the two phases. The second reason is more practical. The action which performs the deployment to Azure Static Web App (the<SPAN>&nbsp;</SPAN><CODE>Azure/static-web-apps-deploy@v0.0.1-preview</CODE><SPAN>&nbsp;</SPAN>one) works only on Linux, while the build process we have executed so far must run on Windows. By using multiple jobs, we can use different environments: the build job will continue to run on a Windows hosted agent, while we're going to run the deployment job on a Linux machine.</P> <P>&nbsp;</P> <P>Let's go and add the second job after the first one we have previously configured:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">deploy:</SPAN> <SPAN class="hljs-attr"> needs:</SPAN> <SPAN class="hljs-string">[build]</SPAN> <SPAN class="hljs-attr"> runs-on:</SPAN> <SPAN class="hljs-string">ubuntu-latest</SPAN> <SPAN class="hljs-attr"> name:</SPAN> <SPAN class="hljs-string">Deploy</SPAN> <SPAN class="hljs-string">Job</SPAN> <SPAN class="hljs-attr"> steps:</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Download</SPAN> <SPAN class="hljs-string">Package</SPAN> <SPAN class="hljs-string">artifact</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">actions/download-artifact@master</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> name:</SPAN> <SPAN class="hljs-string">MSIX</SPAN> <SPAN class="hljs-string">Package</SPAN> <SPAN class="hljs-attr"> path:</SPAN> <SPAN class="hljs-string">MSIX</SPAN> <SPAN class="hljs-attr"> - name:</SPAN> <SPAN class="hljs-string">Build</SPAN> <SPAN class="hljs-string">And</SPAN> <SPAN class="hljs-string">Deploy</SPAN> <SPAN class="hljs-attr"> id:</SPAN> <SPAN class="hljs-string">builddeploy</SPAN> <SPAN class="hljs-attr"> uses:</SPAN> <SPAN class="hljs-string">Azure/static-web-apps-deploy@v0.0.1-preview</SPAN> <SPAN class="hljs-attr"> with:</SPAN> <SPAN class="hljs-attr"> azure_static_web_apps_api_token:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_CALM_DESERT_05F99C503</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-attr"> repo_token:</SPAN> <SPAN class="hljs-string">${{</SPAN> <SPAN class="hljs-string">secrets.GITHUB_TOKEN</SPAN> <SPAN class="hljs-string">}}</SPAN> <SPAN class="hljs-comment"># Used for Github integrations (i.e. PR comments)</SPAN> <SPAN class="hljs-attr"> action:</SPAN> <SPAN class="hljs-string">"upload"</SPAN> <SPAN class="hljs-comment">###### Repository/Build Configurations - These values can be configured to match you app requirements. ######</SPAN> <SPAN class="hljs-comment"># For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig</SPAN> <SPAN class="hljs-attr"> app_location:</SPAN> <SPAN class="hljs-string">"MSIX"</SPAN> <SPAN class="hljs-comment"># App source code path</SPAN> <SPAN class="hljs-comment">###### End of Repository/Build Configurations ######</SPAN> </CODE></PRE> <P>This job contains only two tasks:</P> <P>&nbsp;</P> <UL> <LI> <P>The first one uses the action<SPAN>&nbsp;</SPAN><CODE>actions/download-artifact</CODE><SPAN>&nbsp;</SPAN>to download on the machine the artifact we have just created in the build job. The<SPAN>&nbsp;</SPAN><CODE>name</CODE><SPAN>&nbsp;</SPAN>property must match the value of the<SPAN>&nbsp;</SPAN><CODE>name</CODE><SPAN>&nbsp;</SPAN>property we have set in the<SPAN>&nbsp;</SPAN><CODE>actions/upload-artifact</CODE><SPAN>&nbsp;</SPAN>task of the build job.</P> </LI> <LI> <P>The second one is the task we have previously copied from the original workflow file, which takes care to do the deployment to the Azure Static Web App. Compared to the original task, we have to make a couple of changes:</P> <UL> <LI>The<SPAN>&nbsp;</SPAN><CODE>app_location</CODE><SPAN>&nbsp;</SPAN>parameter must be set with the name of the folder that contains the artifact we need to deploy. In our case, it's<SPAN>&nbsp;</SPAN><CODE>MSIX</CODE>, which is the value we have set as<SPAN>&nbsp;</SPAN><CODE>path</CODE><SPAN>&nbsp;</SPAN>in the<SPAN>&nbsp;</SPAN><CODE>actions/download-artifact</CODE><SPAN>&nbsp;</SPAN>task.</LI> <LI>We can remove the<SPAN>&nbsp;</SPAN><CODE>api_location</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>app_artifact_location</CODE><SPAN>&nbsp;</SPAN>properties, since we aren't deploying a full static web app.</LI> </UL> </LI> </UL> <P>As you can notice, we have configured the job to run on Linux (<CODE>runs-on: ubuntu-latest</CODE>) and we have used the<SPAN>&nbsp;</SPAN><CODE>needs</CODE><SPAN>&nbsp;</SPAN>parameter to specify that this task must run only after the<SPAN>&nbsp;</SPAN><CODE>build</CODE><SPAN>&nbsp;</SPAN>one has been completed. Without this option, GitHub would try to run both jobs in parallel.</P> <P>&nbsp;</P> <P>That's it! Now try to commit any change to the code to trigger the execution of the workflow. If you did everything in the correct way, once the workflow is completed, you should be able to open your browser on the URL assigned to your Azure Static Web App and see the web page generated by Visual Studio to trigger the installation:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ContosoApp.png" style="width: 713px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197918i1D1C11D919BDDDE7/image-size/large?v=v2&amp;px=999" role="button" title="ContosoApp.png" alt="ContosoApp.png" /></span></P> <P>&nbsp;</P> <P><SPAN>By clicking on the&nbsp;</SPAN><STRONG>Get the app</STRONG><SPAN>&nbsp;button, you will trigger the process to start the installation:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ContosoAppInstallation.png" style="width: 650px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197919i4B5D953FC21EBED8/image-size/large?v=v2&amp;px=999" role="button" title="ContosoAppInstallation.png" alt="ContosoAppInstallation.png" /></span></SPAN></P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>In this blog post we have seen how to use the recently announced Azure Static Web App service to host our MSIX packages and enable an easy installation process of our desktop apps through a website. By tweaking the project we can also enable automatic updates: we just need to configure the AppInstaller file<SPAN>&nbsp;</SPAN><A href="#" target="_blank">to support automatic updates</A><SPAN>&nbsp;</SPAN>and use a tool like<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Nerdbank.GitVersioning</A><SPAN>&nbsp;</SPAN>to handle the versioning of the MSIX packages, so that we're sure that every new generated package will have a higher version number.</P> <P>&nbsp;</P> <P>The sample project used in this blog post (including the GitHub workflow) is available<SPAN>&nbsp;</SPAN><A href="#" target="_blank">here</A>. If, instead, you want to see a more advanced approach, you can take a look<SPAN>&nbsp;</SPAN><A href="#" target="_blank">at another project from me</A>. This second repository contains a more complete workflow, which includes versioning management and signing using Azure Key Vault.</P> <P>&nbsp;</P> <P>A special thanks to <A href="#" target="_self">Rafael Rivera</A> and <A href="#" target="_self">Mitchell Webster</A> for the help in troubleshooting and fixing <A href="#" target="_self">a series of issues</A> that were preventing Azure Static Web Apps to work properly with MSIX deployment.</P> <P>&nbsp;</P> <P>Happy deployment!</P> Wed, 10 Jun 2020 14:59:06 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/enable-ci-cd-for-windows-apps-with-github-actions-and-azure/ba-p/1453991 Matteo Pagani 2020-06-10T14:59:06Z Modernization Story: Windows Forms to .NET Core, XAML Islands and MSIX Core https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/modernization-story-windows-forms-to-net-core-xaml-islands-and/ba-p/1451944 <P><LI-VIDEO vid="https://youtu.be/FvzYTI3Adh0" align="center" size="medium" width="400" height="225" uploading="false" thumbnail="https://i.ytimg.com/vi/FvzYTI3Adh0/hqdefault.jpg" external="url"></LI-VIDEO></P> <P>&nbsp;</P> <P>In this customer scenario, the customer has a Windows Forms application built on .NET Framework. They are interested in “modernizing” the user interface to support touch and ink in addition to support for high DPI monitors. In addition they are interested in leveraging MSIX for deployment. A few twists: their current implementation dynamically creates and adds controls to their form at runtime. The “definition” of their form is serialized to xml – as their line of business application is very dynamic with many different “forms” generated at runtime. This proof of concept should also do the same. Another complexity – they have not migrated all of their users to Windows 10 yet, many are still on Windows 7. The interim solution should be a stepping stone towards modernization demonstrating a single package that will work on both Windows 7 and Windows 10 and if the modern controls are not available – fall back to the standard .NET Framework implementation.</P> <P>&nbsp;</P> <P><EM>Let’s break this Proof of concept into 4 parts, as shown in the video:</EM></P> <UL> <LI>Step 1 - Recreate the current Windows Forms application (simplified)</LI> <LI>Step 2 - Migrate it to Core &amp; enable high-DPI.</LI> <LI>Step 3 - Add Xaml Islands &amp; dynamic controls</LI> <LI>Step 4 - Add MSIX packaging (and MSIX Core)</LI> </UL> <P>&nbsp;</P> <P><FONT size="5">Step 2 - Migrate it to Core &amp; enable high-DPI.</FONT></P> <P>I’m going to skip Step 1 because it is just regular Windows Forms, .NET Framework 4.7.2, standard stuff. Step 2 is where things start to get interesting.&nbsp; In September of 2019 Microsoft released .NET Core support for building Windows desktop applications which includes WPF and Windows Forms. .NET Core is the future of .NET. All the major improvements will be made in .NET Core (not .NET Framework). Migrating to Core brings improved runtime performance, single .exe files, smaller app size, self-contained deployment and side-by-side deployment.&nbsp;</P> <P><BR />If this had been a more complex project, perhaps with Nuget packages, we would have used the <A href="#" target="_blank" rel="noopener">try-convert</A> tool route.&nbsp; Instead, I directly edited my project file by right-clicking the project in Visual Studio 2019 Preview, and selecting "Edit Project File".</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="editp.gif" style="width: 444px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197693i11AFB6574CA3482D/image-size/large?v=v2&amp;px=999" role="button" title="editp.gif" alt="editp.gif" /></span>&nbsp;</P> <P>&nbsp;</P> <P>.NET Core has a vastly simplified <A href="#" target="_self">project system</A> and project file.&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="html">&lt;Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"&gt; &lt;PropertyGroup&gt; &lt;OutputType&gt;WinExe&lt;/OutputType&gt; &lt;TargetFramework&gt;netcoreapp3.1&lt;/TargetFramework&gt; &lt;UseWindowsForms&gt;true&lt;/UseWindowsForms&gt; &lt;ApplicationManifest&gt;app.manifest&lt;/ApplicationManifest&gt; &lt;Nullable&gt;annotations&lt;/Nullable&gt; &lt;Platforms&gt;AnyCPU;x64&lt;/Platforms&gt; &lt;/PropertyGroup&gt; &lt;PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"&gt; &lt;PlatformTarget&gt;x64&lt;/PlatformTarget&gt; &lt;/PropertyGroup&gt; &lt;PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"&gt; &lt;PlatformTarget&gt;x64&lt;/PlatformTarget&gt; &lt;/PropertyGroup&gt; &lt;ItemGroup&gt; &lt;PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls" Version="6.1.0-preview1" /&gt; &lt;PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls.WebView" Version="6.1.0-preview1" /&gt; &lt;PackageReference Include="Microsoft.Toolkit.Forms.UI.XamlHost" Version="6.1.0-preview1" /&gt; &lt;/ItemGroup&gt; &lt;Target Name="CheckIfShouldKillVBCSCompiler" /&gt; &lt;/Project&gt;</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>You might wonder about the target <A href="#" target="_self">CheckIfShouldKillVBCSCompiler</A>, I ran into a <A href="#" target="_self">problem</A> compiling with .NET Core compiling with the XamlHost, that target fixed it.</P> <P>I also added an app.manifest file with DPI settings. The combination of below two tags have the following effect : 1) Per-Monitor for &gt;= Windows 10 Anniversary Update,&nbsp;2) System &lt; Windows 10 Anniversary Update. This indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher DPIs, thus preventing the rasterization. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need to opt in but Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should also set the '<A href="#" target="_self">EnableWindowsFormsHighDpiAutoResizing</A>' setting to 'true' in their app.config.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="html"> &lt;application xmlns="urn:schemas-microsoft-com:asm.v3"&gt; &lt;windowsSettings&gt; &lt;dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"&gt;true/PM&lt;/dpiAware&gt; &lt;dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"&gt;PerMonitorV2, PerMonitor&lt;/dpiAwareness&gt; &lt;/windowsSettings&gt; &lt;/application&gt;</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In addition, in our Program Main() I added:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="csharp">Application.SetHighDpiMode(HighDpiMode.SystemAware); //-- setting DPI aware</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>And finally, our application <A href="#" target="_self">configuration</A>.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="html">&lt;?xml version="1.0" encoding="utf-8" ?&gt; &lt;configuration&gt; &lt;startup&gt; &lt;supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /&gt; &lt;/startup&gt; &lt;System.Windows.Forms.ApplicationConfigurationSection&gt; &lt;add key="DpiAwareness" value="PerMonitorV2"/&gt; &lt;/System.Windows.Forms.ApplicationConfigurationSection&gt; &lt;system.codedom&gt; &lt;compilers&gt; &lt;compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, internalKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701"/&gt; &lt;compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.0.0, Culture=neutral, internalKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&amp;quot;Web\&amp;quot; /optionInfer+"/&gt; &lt;/compilers&gt; &lt;/system.codedom&gt; &lt;/configuration&gt;</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>There were a few other minor adjustments shown in the video.</P> <P>&nbsp;</P> <P><FONT size="5">Step 3 - Add Xaml Islands &amp; dynamic controls</FONT></P> <P>For this step we introduce <A href="#" target="_self">XAML Islands</A>. Most of the cool new touch-enabled features for Windows 10 have been supported for UWP (Universal Windows Platform) apps only. Not anymore. With XAML Islands we can now call UWP UI APIs from the .NET framework.&nbsp;</P> <P>&nbsp;</P> <P>There are three nuget packages we will leverage that make it even easier to implement XAML Islands in our Windows Forms application.</P> <P>First is&nbsp;<A href="#" target="_self">Microsoft.Toolkit.Forms.UI.Controls</A>. This package is just chock full of wonderful awesomesauce. With it we get five pre-wrapped controls - with properties and event handlers expose. We will make use of four out of the five in our demo.&nbsp;It gives us the&nbsp;<A href="#" target="_self">InkCanvas</A> which receives and displays ink strokes, the <A href="#" target="_self">InkToolbar</A> which shows a collection of buttons that activate ink-related features. The&nbsp;<A href="#" target="_self">MapControl</A> which is for rendering digital maps from different providers (like OpenStreetMap and Bing Maps) and various types of map overlays. Also the <A href="#" target="_self">MediaPlayerElement</A> which embeds a view that streams and renders media content such as video. Finally, although we are not using it in our demo, this library also includes the&nbsp;<A href="#" target="_self">SwapChainPanel</A>&nbsp;which provides a hosting surface for DirectX swap chains to provide content that can be rendered into a XAML UI - that opens the doors for some super exciting advanced animation capabilities.</P> <P>&nbsp;</P> <P>We are also leveraging the&nbsp;<A href="#" target="_self">Microsoft.Toolkit.Forms.UI.Controls.WebView</A>&nbsp;package, which provides a WebView XAML control for for web content that unlike the <A href="#" target="_self">WebBrowser</A> control in Windows Forms this one renders richly formatted HTML5 content, dynamically generated code, or even content files.</P> <P>&nbsp;</P> <P>Lastly we leverage the&nbsp;<A href="#" target="_self">Microsoft.Toolkit.Forms.UI.XamlHost</A>&nbsp;library which provides interop helpers for to easily leverage the built-in UWP controls such as <A href="#" target="_self">TextBox</A>, <A href="#" target="_self">ComboBox</A>, <A href="#" target="_self">Button</A>, <A href="#" target="_self">Calendar</A>. All of the UWP controls that are touch friendly out-of-the-box.&nbsp;&nbsp;</P> <P>&nbsp;</P> <P>Then we implemented a simple factory pattern:</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="csharp"> ControlFactory factory = (isXaml) ? (ControlFactory)(new ControlFactoryXaml()) : (ControlFactory)(new ControlFactoryNet());</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><FONT size="5">Step 4 - Add MSIX packaging (and MSIX Core)</FONT></P> <P>Finally in step four we added MSIX packaging and then MSIX Core support.</P> <P>First I added what we refer to as a "WAP" (pronounced <EM>whap</EM>) project, or <A href="#" target="_self">Windows Application Packaging</A> project. Then set that as your start project, and add an application reference to our Windows Forms project, like this:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="WAP.gif" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197719iAC0523EA09B4C370/image-size/large?v=v2&amp;px=999" role="button" title="WAP.gif" alt="WAP.gif" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>And finally, in order to support <A href="#" target="_self">MSIX Core</A> (so we can install this on Windows 7 SP1), we added that <A href="#" target="_self">TargetDeviceFamily</A> to our Dependencies node in our <A href="#" target="_self">Package.appxmanifest</A> like this.</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="html"> &lt;TargetDeviceFamily Name="MSIXCore.Desktop" MinVersion="6.1.7601.0" MaxVersionTested="10.0.10240.0" /&gt; </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>Find the full sample code here:</P> <P><FONT size="5"><A href="#" target="_blank" rel="noopener">http://aka.ms/modernizationstory</A>&nbsp;</FONT></P> <P>&nbsp;</P> <P><FONT size="5">Enjoy!</FONT></P> <P>&nbsp;</P> <P>&nbsp;</P> Tue, 09 Jun 2020 21:12:28 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/modernization-story-windows-forms-to-net-core-xaml-islands-and/ba-p/1451944 Robert_Evans 2020-06-09T21:12:28Z Create a Windows module for React Native with asynchronous code in C# https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-windows-module-for-react-native-with-asynchronous-code/ba-p/1448692 <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-a-react-native-module-for-windows/ba-p/1067893" target="_blank" rel="noopener">We have already explored on this blog</A><SPAN>&nbsp;</SPAN>the opportunity to create native Windows modules for React Native. Thanks to these modules, you are able to surface native Windows APIs to JavaScript, so that you can leverage them from a React Native application running on Windows. Native modules are one of the many ways supported by React Native to build truly native applications. Despite you're using web technologies, you are able to get access to native features of the underlying platform, like notifications, storage, geolocation, etc.</P> <P>&nbsp;</P> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-a-react-native-module-for-windows/ba-p/1067893" target="_blank" rel="noopener">In the previous post</A><SPAN>&nbsp;</SPAN>we have learned the basics on how to create a module in C# and how to register it in the main application, so that the C# functions are exposed as JavaScript functions. As such, in this post I won't go again into the details on how to build a module and how to register it in the Windows implementation of the React Native app. However, recently, I came across a blocker while working on a customer's project related to this scenario. My post (like the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">official documentation</A>) was leveraging synchronous methods. In my scenario, instead, I needed to use the Geolocation APIs provided by Windows 10, which are asynchronous and based on the async / await pattern.</P> <P>&nbsp;</P> <P>As such, I've started to build my module using the standard async / await pattern:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-keyword">namespace</SPAN> <SPAN class="hljs-title">GeolocationModule</SPAN> { [<SPAN class="hljs-meta">ReactModule</SPAN>] <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">GeolocationModule</SPAN> { [<SPAN class="hljs-meta">ReactMethod(<SPAN class="hljs-meta-string">"getCoordinates"</SPAN>)</SPAN>] <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">async</SPAN> Task&lt;<SPAN class="hljs-keyword">string</SPAN>&gt; <SPAN class="hljs-title">GetCoordinates</SPAN>()</SPAN> { Geolocator geolocator = <SPAN class="hljs-keyword">new</SPAN> Geolocator(); <SPAN class="hljs-keyword">var</SPAN> position = <SPAN class="hljs-keyword">await</SPAN> geolocator.GetGeopositionAsync(); <SPAN class="hljs-keyword">string</SPAN> result = <SPAN class="hljs-string">$"Latitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Latitude}</SPAN> - Longitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Longitude}</SPAN>"</SPAN>; <SPAN class="hljs-keyword">return</SPAN> result; } } } </CODE></PRE> <P>This is the traditional implementation of this pattern:</P> <UL> <LI>The<SPAN>&nbsp;</SPAN><CODE>GetCoordinates()</CODE><SPAN>&nbsp;</SPAN>method is marked with the<SPAN>&nbsp;</SPAN><CODE>async</CODE><SPAN>&nbsp;</SPAN>keyword</LI> <LI>The<SPAN>&nbsp;</SPAN><CODE>GetCoordinates()</CODE><SPAN>&nbsp;</SPAN>method returns<SPAN>&nbsp;</SPAN><CODE>Task&lt;T&gt;</CODE>, where<SPAN>&nbsp;</SPAN><CODE>T</CODE><SPAN>&nbsp;</SPAN>is the type of the result we want to return (in our case, its' a<SPAN>&nbsp;</SPAN><CODE>string</CODE>)</LI> <LI>When we call an asynchronous API in the body (the<SPAN>&nbsp;</SPAN><CODE>GetGeopositionAsync()</CODE><SPAN>&nbsp;</SPAN>method exposed by the<SPAN>&nbsp;</SPAN><CODE>Geolocator</CODE><SPAN>&nbsp;</SPAN>object), we add the<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>prefix.</LI> </UL> <P>Then, in the React Native portion, I've created a wrapper for this module using the<SPAN>&nbsp;</SPAN><CODE>NativeModules</CODE><SPAN>&nbsp;</SPAN>APIs, exactly like I did in my previous sample:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">export</SPAN> <SPAN class="hljs-keyword">const</SPAN> getCoordinates = <SPAN class="hljs-function"><SPAN class="hljs-params">()</SPAN> =&gt;</SPAN> { <SPAN class="hljs-keyword">return</SPAN> <SPAN class="hljs-keyword">new</SPAN> <SPAN class="hljs-built_in">Promise</SPAN>(<SPAN class="hljs-function">(<SPAN class="hljs-params">resolve, reject</SPAN>) =&gt;</SPAN> { NativeModules.GeolocationModule.getCoordinates(<SPAN class="hljs-function"><SPAN class="hljs-keyword">function</SPAN>(<SPAN class="hljs-params">result, error</SPAN>) </SPAN>{ <SPAN class="hljs-keyword">if</SPAN> (error) { reject(error); } <SPAN class="hljs-keyword">else</SPAN> { resolve(result); } }) }) } </CODE></PRE> <P>Once I've connected all the dots and launched the React Native application, however, I was greeted with an unpleasant surprise. As soon as I hit the button which was invoking the<SPAN>&nbsp;</SPAN><CODE>getCoordinates()</CODE><SPAN>&nbsp;</SPAN>function, the application was crashing.</P> <P>&nbsp;</P> <P>Thanks to a chat with<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Vladimir Morozov</A><SPAN>&nbsp;</SPAN>from the React Native team, it turned out that React Native doesn't support methods which return a<SPAN>&nbsp;</SPAN><CODE>Task&lt;T&gt;</CODE>. In order to work properly, they must return<SPAN>&nbsp;</SPAN><CODE>void</CODE>. How is it possible to achieve this goal and, at the same time, being able to keep calling asynchronous APIs, like the ones exposed by the<SPAN>&nbsp;</SPAN><CODE>Geolocator</CODE><SPAN>&nbsp;</SPAN>class? Thanks to Vladimir who put me on the right track, the solution is easy. Let's explore the options we have.</P> <H3 id="use-promises">Use promises</H3> <P>When it comes to JavaScript, I'm a big fan of<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">promises</A><SPAN>&nbsp;</SPAN>since they enable a syntax which is very similar to the C# one. When an asynchronous method returns a Promise, you can simply mark the function with the<SPAN>&nbsp;</SPAN><CODE>async</CODE><SPAN>&nbsp;</SPAN>keyword and add the<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>prefix before calling the asynchronous function, like in the following sample:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">const</SPAN> getData = <SPAN class="hljs-keyword">async</SPAN> () =&gt; { <SPAN class="hljs-keyword">var</SPAN> result = <SPAN class="hljs-keyword">await</SPAN> fetch (<SPAN class="hljs-string">'this-is-a-url'</SPAN>); <SPAN class="hljs-comment">//do something with the result</SPAN> } </CODE></PRE> <P>Let's start to see how we can build our module so that it can return a promise. It's easy, thanks to the<SPAN>&nbsp;</SPAN><CODE>IReactPromise</CODE><SPAN>&nbsp;</SPAN>interface included in the React Native implementation for Windows. Let's see some code first:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-keyword">using</SPAN> Microsoft.ReactNative.Managed; <SPAN class="hljs-keyword">using</SPAN> System; <SPAN class="hljs-keyword">using</SPAN> Windows.Devices.Geolocation; <SPAN class="hljs-keyword">namespace</SPAN> <SPAN class="hljs-title">GeolocationModule</SPAN> { [<SPAN class="hljs-meta">ReactModule</SPAN>] <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">GeolocationModule</SPAN> { [<SPAN class="hljs-meta">ReactMethod(<SPAN class="hljs-meta-string">"getCoordinatesWithPromise"</SPAN>)</SPAN>] <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">async</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">GetCoordinatesWithPromise</SPAN>(<SPAN class="hljs-params">IReactPromise&lt;<SPAN class="hljs-keyword">string</SPAN>&gt; promise</SPAN>)</SPAN> { <SPAN class="hljs-keyword">try</SPAN> { Geolocator geolocator = <SPAN class="hljs-keyword">new</SPAN> Geolocator(); <SPAN class="hljs-keyword">var</SPAN> position = <SPAN class="hljs-keyword">await</SPAN> geolocator.GetGeopositionAsync(); <SPAN class="hljs-keyword">string</SPAN> result = <SPAN class="hljs-string">$"Latitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Latitude}</SPAN> - Longitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Longitude}</SPAN>"</SPAN>; promise.Resolve(result); } <SPAN class="hljs-keyword">catch</SPAN> (Exception e) { promise.Reject(<SPAN class="hljs-keyword">new</SPAN> ReactError { Exception = e }); } } } } </CODE></PRE> <P>As first step, we need to declare the method as<SPAN>&nbsp;</SPAN><CODE>async void</CODE>, in order to be compliant with the React Native requirements. To handle the asynchronous nature of the method, the key component is the requested parameter, which type is<SPAN>&nbsp;</SPAN><CODE>IReactPromise&lt;T&gt;</CODE>, where<SPAN>&nbsp;</SPAN><CODE>T</CODE><SPAN>&nbsp;</SPAN>is the type of the value we want to return. In my scenario I want to return a string with the full coordinates, so I'm using the<SPAN>&nbsp;</SPAN><CODE>IReactPromise&lt;string&gt;</CODE><SPAN>&nbsp;</SPAN>type.</P> <P>&nbsp;</P> <P>Inside the method we can start writing our code like we would do in a traditional Windows application. Since we have marked the method with the<SPAN>&nbsp;</SPAN><CODE>async</CODE><SPAN>&nbsp;</SPAN>keyword, we can just call any asynchronous API (like the<SPAN>&nbsp;</SPAN><CODE>GetGeopositionAsync()</CODE><SPAN>&nbsp;</SPAN>one exposed by the<SPAN>&nbsp;</SPAN><CODE>Geolocator</CODE><SPAN>&nbsp;</SPAN>class) by adding the<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>prefix.</P> <P>&nbsp;</P> <P>Once we have completed the work and we have obtained the result we want to return, we need to pass it to the<SPAN>&nbsp;</SPAN><CODE>Resolve()</CODE><SPAN>&nbsp;</SPAN>method exposed by the<SPAN>&nbsp;</SPAN><CODE>IReactPromise</CODE><SPAN>&nbsp;</SPAN>parameter. The method expects a value which type is equal to<SPAN>&nbsp;</SPAN><CODE>T</CODE>. In my case, for example, we would get an error if we try to pass anything but a string.</P> <P>&nbsp;</P> <P>In case something goes wrong, instead, we can use the<SPAN>&nbsp;</SPAN><CODE>Reject()</CODE><SPAN>&nbsp;</SPAN>method to surface the error to the React Native application, by creating a new<SPAN>&nbsp;</SPAN><CODE>ReactError</CODE><SPAN>&nbsp;</SPAN>object. You can customize it with different parameters, like<SPAN>&nbsp;</SPAN><CODE>Code</CODE>,<SPAN>&nbsp;</SPAN><CODE>Message</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>UserInfo</CODE>. In my case, I just want to raise the whole exception, so I simply set the<SPAN>&nbsp;</SPAN><CODE>Exception</CODE><SPAN>&nbsp;</SPAN>property exposed by my<SPAN>&nbsp;</SPAN><CODE>try / catch</CODE><SPAN>&nbsp;</SPAN>block.</P> <P>&nbsp;</P> <P>That's it! Now we can easily define a function in our React Native code that, by using the<SPAN>&nbsp;</SPAN><CODE>NativeModules</CODE><SPAN>&nbsp;</SPAN>API and a Promise, can invoke the C# method we have just created, as in the following sample:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">const</SPAN> getCoordinatesWithPromise = <SPAN class="hljs-keyword">async</SPAN> () =&gt; { <SPAN class="hljs-keyword">var</SPAN> coordinates = <SPAN class="hljs-keyword">await</SPAN> NativeModules.GeolocationModule.getCoordinatesWithPromise(); <SPAN class="hljs-built_in">console</SPAN>.log(coordinates); } </CODE></PRE> <P>Since we're using a Promise, the syntax is the same we have seen in the previous sample. We mark the function with the<SPAN>&nbsp;</SPAN><CODE>async</CODE><SPAN>&nbsp;</SPAN>keyword and we call the<SPAN>&nbsp;</SPAN><CODE>getCoordinatesWithPromise()</CODE><SPAN>&nbsp;</SPAN>method with the<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>prefix. Thanks to the Promise we can be sure that, when we invoke the<SPAN>&nbsp;</SPAN><CODE>console.log()</CODE><SPAN>&nbsp;</SPAN>function, the<SPAN>&nbsp;</SPAN><CODE>coordinates</CODE><SPAN>&nbsp;</SPAN>property has been properly set. The native module is exposed through the<SPAN>&nbsp;</SPAN><CODE>NativeModules</CODE><SPAN>&nbsp;</SPAN>APIs and the<SPAN>&nbsp;</SPAN><CODE>GeolocationModule</CODE><SPAN>&nbsp;</SPAN>object, which is the name of the C# class we have created (if you remember, we have marked it with the<SPAN>&nbsp;</SPAN><CODE>[ReactModule]</CODE><SPAN>&nbsp;</SPAN>attribute).</P> <P>&nbsp;</P> <H3 id="use-callbacks">Use callbacks</H3> <P>I really much prefer the syntax offered by Promises but if, by any chance, you prefer to use callbacks, we got you covered! You can use, in fact, a slightly different syntax, based on the<SPAN>&nbsp;</SPAN><CODE>Action&lt;T&gt;</CODE><SPAN>&nbsp;</SPAN>object, to expose your native C# method as a callback. Let's see the implementation:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-121" class="language-csharp hljs"><SPAN class="hljs-keyword">using</SPAN> Microsoft.ReactNative.Managed; <SPAN class="hljs-keyword">using</SPAN> System; <SPAN class="hljs-keyword">using</SPAN> Windows.Devices.Geolocation; <SPAN class="hljs-keyword">namespace</SPAN> <SPAN class="hljs-title">GeolocationModule</SPAN> { [<SPAN class="hljs-meta">ReactModule</SPAN>] <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">GeolocationModule</SPAN> { [<SPAN class="hljs-meta">ReactMethod(<SPAN class="hljs-meta-string">"getCoordinatesWithCallback"</SPAN>)</SPAN>] <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">async</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">GetCoordinatesWithCallback</SPAN>(<SPAN class="hljs-params">Action&lt;<SPAN class="hljs-keyword">string</SPAN>&gt; resolve, Action&lt;<SPAN class="hljs-keyword">string</SPAN>&gt; reject</SPAN>)</SPAN> { <SPAN class="hljs-keyword">try</SPAN> { Geolocator geolocator = <SPAN class="hljs-keyword">new</SPAN> Geolocator(); <SPAN class="hljs-keyword">var</SPAN> position = <SPAN class="hljs-keyword">await</SPAN> geolocator.GetGeopositionAsync(); <SPAN class="hljs-keyword">string</SPAN> result = <SPAN class="hljs-string">$"Latitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Latitude}</SPAN> - Longitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Longitude}</SPAN>"</SPAN>; resolve(result); } <SPAN class="hljs-keyword">catch</SPAN> (Exception e) { reject(e.Message); } } } } </CODE></PRE> <P>In this case, as parameters of the method, we are passing two objects which type is<SPAN>&nbsp;</SPAN><CODE>Action&lt;T&gt;</CODE>. The first one will be used when the method completes successfully, the second one when instead something goes wrong. In this case,<SPAN>&nbsp;</SPAN><CODE>T</CODE><SPAN>&nbsp;</SPAN>is a<SPAN>&nbsp;</SPAN><CODE>string</CODE><SPAN>&nbsp;</SPAN>in both cases. In case of success, we want to return the usual string with the full coordinates; in case of failure, we want to return the message of the exception.</P> <P>&nbsp;</P> <P>The rest of the code is similar to the one we have written before. The only difference is that, when we have achieved our result, we just pass it to the<SPAN>&nbsp;</SPAN><CODE>resolve()</CODE><SPAN>&nbsp;</SPAN>function; when something goes wrong, instead, we call the<SPAN>&nbsp;</SPAN><CODE>reject()</CODE><SPAN>&nbsp;</SPAN>function. The main difference compared to the previous approach is that<SPAN>&nbsp;</SPAN><CODE>Action&lt;T&gt;</CODE><SPAN>&nbsp;</SPAN>isn't a structured object like the<SPAN>&nbsp;</SPAN><CODE>IReactNative&lt;T&gt;</CODE><SPAN>&nbsp;</SPAN>interface. As such, it doesn't support to pass the full exception but, in this case, we choose to pass only the<SPAN>&nbsp;</SPAN><CODE>Message</CODE><SPAN>&nbsp;</SPAN>property of the exception.</P> <P>&nbsp;</P> <P>That's if from the C# side. Let's move to the JavaScript one. In this case, being a callback, we can't use anymore the<SPAN>&nbsp;</SPAN><CODE>async</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>keywords, but we need to pass to the method two functions: one will be called when the operation is successful, one when we get an error.</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-158" class="language-javascript hljs"><SPAN class="hljs-keyword">const</SPAN> getCoordinatesWithCallback = <SPAN class="hljs-function"><SPAN class="hljs-params">()</SPAN> =&gt;</SPAN> { NativeModules.GeolocationModule.getCoordinatesWithCallback( <SPAN class="hljs-function">(<SPAN class="hljs-params">result</SPAN>) =&gt;</SPAN> { <SPAN class="hljs-built_in">console</SPAN>.log(result); }, (error) =&gt; <SPAN class="hljs-built_in">console</SPAN>.log(error)); }</CODE></PRE> <P>&nbsp;</P> <H3 id="passing-parameters">Passing parameters</H3> <P>In both scenarios, if we need to pass any parameter from the JavaScript code to the C# one we can just add them before the<SPAN>&nbsp;</SPAN><CODE>IReactPromise&lt;T&gt;</CODE><SPAN> or the&nbsp;<CODE>Action&lt;T&gt;</CODE>&nbsp;ones</SPAN>. For example, let's say we want to set the desired accuracy of the<SPAN>&nbsp;</SPAN><CODE>Geolocator</CODE><SPAN>&nbsp;</SPAN>object with a value in meters, passed from the React Native app. We can just define the method like this:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-167" class="language-csharp hljs">[<SPAN class="hljs-meta">ReactMethod(<SPAN class="hljs-meta-string">"getCoordinatesWithPromise"</SPAN>)</SPAN>] <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">async</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">GetCoordinatesWithPromise</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">uint</SPAN> meters, IReactPromise &lt;<SPAN class="hljs-keyword">string</SPAN>&gt; promise</SPAN>)</SPAN> { <SPAN class="hljs-keyword">try</SPAN> { Geolocator geolocator = <SPAN class="hljs-keyword">new</SPAN> Geolocator(); geolocator.DesiredAccuracyInMeters = meters; <SPAN class="hljs-keyword">var</SPAN> position = <SPAN class="hljs-keyword">await</SPAN> geolocator.GetGeopositionAsync(); <SPAN class="hljs-keyword">string</SPAN> result = <SPAN class="hljs-string">$"Latitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Latitude}</SPAN> - Longitude: <SPAN class="hljs-subst">{position.Coordinate.Point.Position.Longitude}</SPAN>"</SPAN>; promise.Resolve(result); } <SPAN class="hljs-keyword">catch</SPAN> (Exception e) { promise.Reject(<SPAN class="hljs-keyword">new</SPAN> ReactError { Exception = e }); } } </CODE></PRE> <P>Then, from JavaScript, we just need to pass the value of the parameter when we invoke the<SPAN>&nbsp;</SPAN><CODE>getCoordinatesWithPromise()</CODE><SPAN>&nbsp;</SPAN>function:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-191" class="language-javascript hljs"><SPAN class="hljs-keyword">const</SPAN> getCoordinatesWithPromise = <SPAN class="hljs-keyword">async</SPAN> () =&gt; { <SPAN class="hljs-keyword">var</SPAN> coordinates = <SPAN class="hljs-keyword">await</SPAN> NativeModules.GeolocationModule.getCoordinatesWithPromise(<SPAN class="hljs-number">15</SPAN>); <SPAN class="hljs-built_in">console</SPAN>.log(coordinates); } </CODE></PRE> <P>Of course, you can use the same pattern if you prefer to leverage the callback approach.</P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>On this blog<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-a-react-native-module-for-windows/ba-p/1067893" target="_blank" rel="noopener">we have already learned</A><SPAN>&nbsp;</SPAN>how to build native C# modules for React Native so that, as a developer, you can access native Windows APIs from JavaScript. However, in that sample we leveraged only synchronous APIs. The reality, however, is that most of the Windows 10 APIs are implemented with the asynchronous pattern. Unfortunately, as soon as you start using them, you will face errors and crashes, since React Native isn't able to properly understand the traditional async / await pattern, based on the Task object. Thanks to the React Native team I've been pointed into the right direction, by leveraging the<SPAN>&nbsp;</SPAN><CODE>IReactNative</CODE><SPAN>&nbsp;</SPAN>interface (in case you want to enable asynchronous APIs with promises) or the<SPAN>&nbsp;</SPAN><CODE>Action</CODE><SPAN>&nbsp;</SPAN>object (in case you want to leverage the more traditional callback approach).</P> <P>&nbsp;</P> <P>Regardless of the approach you prefer, you'll be able to achieve your goal of leveraging asynchronous Windows 10 APIs from your React Native application running on Windows:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FinalApp.png" style="width: 846px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197301i6D55AC3AC86BD3EC/image-size/large?v=v2&amp;px=999" role="button" title="FinalApp.png" alt="FinalApp.png" /></span></P> Tue, 09 Jun 2020 16:37:35 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-windows-module-for-react-native-with-asynchronous-code/ba-p/1448692 Matteo Pagani 2020-06-09T16:37:35Z XAML Shapes manipulation level up https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/xaml-shapes-manipulation-level-up/ba-p/1445405 <DIV id="MainContent"> <P>In the <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899" target="_blank" rel="noopener">previous article</A>, we were able to provide a drag &amp; drop functionality for moving XAML Shapes. We go further: Let's give the ability to the user to move the origin or the end of the line with an anchor. This way, we would be able to change the size, the orientation, the connections of the lines.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="the-baseline-code"><A id="pragma-line-4" target="_blank"></A>The baseline code</H2> <P>The starting point? The <A href="#" target="_blank" rel="noopener">exact same as usual,</A> related to the article <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">Use the UWP Inking platform as input for advanced scenarios</A>.</P> <PRE><CODE id="pragma-line-8" class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Grid</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">InkCanvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt;</SPAN> <SPAN class="hljs-comment">&lt;!-- Canvas for displaying the "recognized" XAML Shapes --&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Canvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"ShapesCanvas"</SPAN> /&gt;</SPAN> <SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Grid</SPAN>&gt;</SPAN> </CODE></PRE> <PRE><CODE id="pragma-line-17" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-title">MainPage</SPAN>()</SPAN> { <SPAN class="hljs-keyword">this</SPAN>.InitializeComponent(); <SPAN class="hljs-comment">// Initialize the InkCanvas</SPAN> inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch; <SPAN class="hljs-comment">// When the user finished to draw something on the InkCanvas</SPAN> inkCanvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected; } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">InkPresenter_StrokesCollected</SPAN>(<SPAN class="hljs-params"> Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args</SPAN>)</SPAN> { InkStroke stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Last(); <SPAN class="hljs-comment">// Action 1 = We use a function that we will implement just after to create the XAML Line</SPAN> Line line = ConvertStrokeToXAMLLine(stroke); <SPAN class="hljs-comment">// Action 2 = We add the Line in the second Canvas</SPAN> ShapesCanvas.Children.Add(line); <SPAN class="hljs-comment">// We delete the InkStroke from the InkCanvas</SPAN> stroke.Selected = <SPAN class="hljs-literal">true</SPAN>; inkCanvas.InkPresenter.StrokeContainer.DeleteSelected(); } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> Line <SPAN class="hljs-title">ConvertStrokeToXAMLLine</SPAN>(<SPAN class="hljs-params">InkStroke stroke</SPAN>)</SPAN> { <SPAN class="hljs-keyword">var</SPAN> line = <SPAN class="hljs-keyword">new</SPAN> Line(); line.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Green); line.StrokeThickness = <SPAN class="hljs-number">6</SPAN>; <SPAN class="hljs-comment">// The origin = (X1, Y1)</SPAN> line.X1 = stroke.GetInkPoints().First().Position.X; line.Y1 = stroke.GetInkPoints().First().Position.Y; <SPAN class="hljs-comment">// The end = (X2, Y2)</SPAN> line.X2 = stroke.GetInkPoints().Last().Position.X; line.Y2 = stroke.GetInkPoints().Last().Position.Y; <SPAN class="hljs-keyword">return</SPAN> line; } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="adding-the-anchors"><A id="pragma-line-65" target="_blank"></A>Adding the anchors</H2> <P>In the <EM>ConvertStrokeToXAMLLine</EM> method, just before returning the <STRONG>line</STRONG> object, we add a handler for the <STRONG>Tapped</STRONG> event which is raised no matter if we use the touch, mouse or pen.</P> <PRE><CODE id="pragma-line-68" class="language-csharp hljs">line.Tapped += Line_Tapped; </CODE></PRE> <P>The <EM>Line_Tapped</EM> will be the place to draw the anchors on the lines ends and initiate the handlers for allowing the drag &amp; drop actions on these anchors.</P> <P>&nbsp;</P> <H3 id="1-get-the-line--some-cosmetics"><A id="pragma-line-74" target="_blank"></A>1. Get the line + some cosmetics</H3> <P>We get the line tapped from the <STRONG>sender</STRONG> object passed as parameter for the event handler. To visually show the selected line, we can modify the stroke or the color. Here is a sample:</P> <PRE><CODE id="pragma-line-76" class="language-csharp hljs">Line line = (Line)sender; line.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.DarkRed); line.StrokeThickness = <SPAN class="hljs-number">10</SPAN>; </CODE></PRE> <P>&nbsp;</P> <H3 id="2-draw-the-origin-and-the-end-of-the-line"><A id="pragma-line-82" target="_blank"></A>2. Draw the origin and the end of the line</H3> <P>To display an anchor at the ends of the lines, we draw a circle by using the <A href="#" target="_blank" rel="noopener">XAML Ellipse class</A> with the same Height and Width. We create 2 circles: <EM>anchorOrigin</EM> and <EM>anchorEnd</EM> for respectively the origin and the end of the line. We add these circles to the canvas with <CODE>ShapesCanvas.Children.Add(MyObject)</CODE>.</P> <PRE><CODE id="pragma-line-84" class="language-csharp hljs"><SPAN class="hljs-keyword">int</SPAN> size_EndLines = <SPAN class="hljs-number">25</SPAN>; <SPAN class="hljs-comment">// Create 2 circles for the ends of the line</SPAN> Ellipse anchorOrigin = <SPAN class="hljs-keyword">new</SPAN> Ellipse { Fill = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.OrangeRed), Height = size_EndLines, Width = size_EndLines }; ShapesCanvas.Children.Add(anchorOrigin); Ellipse anchorEnd = <SPAN class="hljs-keyword">new</SPAN> Ellipse { Fill = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.OrangeRed), Height = size_EndLines, Width = size_EndLines }; ShapesCanvas.Children.Add(anchorEnd); </CODE></PRE> <P>We set the position of each circle in order that its center matches the position of the end of the line. For this, we use two functions to modify the <STRONG>Canvas.Left</STRONG> and <STRONG>Canvas.Top</STRONG> attached properties of the circles:</P> <PRE><CODE id="pragma-line-105" class="language-csharp hljs"><SPAN class="hljs-comment">// Put the anchors at the origin and at the end of the line</SPAN> Canvas.SetLeft(anchorOrigin, line.X1 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); Canvas.SetLeft(anchorEnd, line.X2 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); Canvas.SetTop(anchorOrigin, line.Y1 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); Canvas.SetTop(anchorEnd, line.Y2 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); </CODE></PRE> <P>&nbsp;</P> <H3 id="3-add-the-event-handlers-for-the-circles"><A id="pragma-line-113" target="_blank"></A>3. Add the event handlers for the circles</H3> <P>The strategy here is the exact same as described in the <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899" target="_blank" rel="noopener">previous article</A> with the <A href="#" target="_blank" rel="noopener">Manipulation events</A> applied to both anchors:</P> <UL id="pragma-line-115"> <LI id="pragma-line-115">The <STRONG><A href="#" target="_blank" rel="noopener">ManipulationMode</A></STRONG> property can be used to restraint the move of the shape in only on direction. In our sample, we would like to move on both axis X and Y.</LI> <LI id="pragma-line-116">The <STRONG><A href="#" target="_blank" rel="noopener">ManipulationStarted</A></STRONG> event is raised when we start the drag &amp; drop.</LI> <LI id="pragma-line-117">The <STRONG><A href="#" target="_blank" rel="noopener">ManipulationDelta</A></STRONG> event gives the possibility to give visual feedback for every move during the drag &amp; drop.</LI> <LI id="pragma-line-118">The <STRONG><A href="#" target="_blank" rel="noopener">ManipulationCompleted</A></STRONG> event occurs when the drag &amp; drop is finished.</LI> </UL> <BLOCKQUOTE id="pragma-line-121"> <P><STRONG>Note</STRONG>: For the <EM>ManipulationDelta</EM> and <EM>ManipulationCompleted</EM> events, we are forced to use two different handlers because the code is modifying the position of the ends of the lines and we have to know which end (origin or end) to modify. The handler for the <EM>ManipulationStarted</EM> event is the same for both anchors.</P> </BLOCKQUOTE> <PRE><CODE id="pragma-line-123" class="language-csharp hljs"><SPAN class="hljs-comment">// Enable manipulations on the anchors</SPAN> anchorOrigin.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; anchorOrigin.ManipulationStarted += Anchor_ManipulationStarted; anchorOrigin.ManipulationDelta += Anchor_Origin_ManipulationDelta; anchorOrigin.ManipulationCompleted += Anchor_Origin_ManipulationCompleted; anchorEnd.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; anchorEnd.ManipulationStarted += Anchor_ManipulationStarted; anchorEnd.ManipulationDelta += Anchor_End_ManipulationDelta; anchorEnd.ManipulationCompleted += Anchor_End_ManipulationCompleted; </CODE></PRE> <P>&nbsp;</P> <H3 id="4-keep-track-of-the-selected-line"><A id="pragma-line-136" target="_blank"></A>4. Keep track of the selected line</H3> <P>We need to store some object to make the drag &amp; drop working and to be also able to 'unselect' and 'select' a new line. Let me explain:</P> <UL id="pragma-line-138"> <LI id="pragma-line-138">The line and its initial coordinates (x1,y1) - (x2,y2) are necessary to finalize the move when the drag &amp; drop is complete.</LI> <LI id="pragma-line-139">The anchors of the line have to be deleted when the user click or touch another line.</LI> </UL> <P>To do this, we add a structure and we use it as a field that can be accessible by the code:</P> <PRE><CODE id="pragma-line-142" class="language-csharp hljs"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">struct</SPAN> ActiveLine { <SPAN class="hljs-keyword">public</SPAN> Line line; <SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">double</SPAN> initialX1; <SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">double</SPAN> initialY1; <SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">double</SPAN> initialX2; <SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">double</SPAN> initialY2; <SPAN class="hljs-keyword">public</SPAN> Ellipse AnchorOrigin; <SPAN class="hljs-keyword">public</SPAN> Ellipse AnchorEnd; } <SPAN class="hljs-keyword">private</SPAN> ActiveLine activeLine; </CODE></PRE> <P>Then, we create method to do the initialization:</P> <PRE><CODE id="pragma-line-158" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">InitializeActiveLine</SPAN>(<SPAN class="hljs-params">Line line, Ellipse origin, Ellipse end</SPAN>)</SPAN> { activeLine.line = line; activeLine.initialX1 = line.X1; activeLine.initialY1 = line.Y1; activeLine.initialX2 = line.X2; activeLine.initialY2 = line.Y2; activeLine.AnchorOrigin = origin; activeLine.AnchorEnd = end; } </CODE></PRE> <P>We use this method as the last instruction of the <EM>Line_Tapped</EM> event handler:</P> <PRE><CODE id="pragma-line-172" class="language-csharp hljs">InitializeActiveLine(line, anchorOrigin, anchorEnd); </CODE></PRE> <P>&nbsp;</P> <H3 id="5-unselect-the-line"><A id="pragma-line-177" target="_blank"></A>5. Unselect the line</H3> <P>This step is, in fact the very first we have to do while entering to the <EM>Line_Tapped</EM> event handler</P> <PRE><CODE id="pragma-line-180" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Line_Tapped</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, TappedRoutedEventArgs e</SPAN>)</SPAN> { <SPAN class="hljs-comment">// Remove the anchor from the selected line</SPAN> UnselectActiveLine(); <SPAN class="hljs-comment">// ...</SPAN> </CODE></PRE> <P>The code of <EM>UnselectActiveLine</EM> just put back the original color/stroke of the line and more important: delete the anchors for this line because we would like to have the anchors only for the lines we select.</P> <PRE><CODE id="pragma-line-189" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">UnselectActiveLine</SPAN>()</SPAN> { <SPAN class="hljs-keyword">if</SPAN> (activeLine.line != <SPAN class="hljs-literal">null</SPAN> &amp;&amp; activeLine.AnchorOrigin!= <SPAN class="hljs-literal">null</SPAN> &amp;&amp; activeLine.AnchorEnd != <SPAN class="hljs-literal">null</SPAN>) { activeLine.line.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Green); activeLine.line.StrokeThickness = <SPAN class="hljs-number">6</SPAN>; ShapesCanvas.Children.Remove(activeLine.AnchorOrigin); ShapesCanvas.Children.Remove(activeLine.AnchorEnd); } } </CODE></PRE> <BLOCKQUOTE id="pragma-line-204"> <P><STRONG>Note</STRONG>: This 'unselection' is the way we chose for our code. The other possibility would be to use a 'switch to modification mode' button on the UI in order to go to a different mode: We would deactivate drawing on the InkCanvas and we could create/display all anchors of all lines to allow modifications.</P> </BLOCKQUOTE> <P>&nbsp;</P> <H3 id="6-all-pieces-of-the-line_tapped-event-handler"><A id="pragma-line-207" target="_blank"></A>6. All pieces of the <EM>Line_Tapped</EM> event handler</H3> <P>Let's put together all we just explained. Here is the final code of the <EM>Line_Tapped</EM> method:</P> <PRE><CODE id="pragma-line-210" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Line_Tapped</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, TappedRoutedEventArgs e</SPAN>)</SPAN> { <SPAN class="hljs-comment">// Remove the anchor from the selected line</SPAN> UnselectActiveLine(); Line line = (Line)sender; line.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.DarkRed); line.StrokeThickness = <SPAN class="hljs-number">10</SPAN>; <SPAN class="hljs-keyword">int</SPAN> size_EndLines = <SPAN class="hljs-number">25</SPAN>; <SPAN class="hljs-comment">// Create 2 circles for the ends of the line</SPAN> Ellipse anchorOrigin = <SPAN class="hljs-keyword">new</SPAN> Ellipse { Fill = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.OrangeRed), Height = size_EndLines, Width = size_EndLines }; ShapesCanvas.Children.Add(anchorOrigin); Ellipse anchorEnd = <SPAN class="hljs-keyword">new</SPAN> Ellipse { Fill = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.OrangeRed), Height = size_EndLines, Width = size_EndLines }; ShapesCanvas.Children.Add(anchorEnd); <SPAN class="hljs-comment">// Put the anchors at the origin and at the end of the line</SPAN> Canvas.SetLeft(anchorOrigin, line.X1 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); Canvas.SetLeft(anchorEnd, line.X2 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); Canvas.SetTop(anchorOrigin, line.Y1 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); Canvas.SetTop(anchorEnd, line.Y2 - size_EndLines / <SPAN class="hljs-number">2</SPAN>); <SPAN class="hljs-comment">// Enable manipulations on the anchors</SPAN> anchorOrigin.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; anchorOrigin.ManipulationStarted += Anchor_ManipulationStarted; anchorOrigin.ManipulationDelta += Anchor_Origin_ManipulationDelta; anchorOrigin.ManipulationCompleted += Anchor_Origin_ManipulationCompleted; anchorEnd.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; anchorEnd.ManipulationStarted += Anchor_ManipulationStarted; anchorEnd.ManipulationDelta += Anchor_End_ManipulationDelta; anchorEnd.ManipulationCompleted += Anchor_End_ManipulationCompleted; InitializeActiveLine(line, anchorOrigin, anchorEnd); } </CODE></PRE> <P>&nbsp;</P> <H2 id="the-manipulations-events-for-the-anchors"><A id="pragma-line-260" target="_blank"></A>The manipulations' events for the anchors</H2> <H3 id="initiate-the-manipulation"><A id="pragma-line-262" target="_blank"></A>Initiate the manipulation</H3> <P>As described and explained in the <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899" target="_blank" rel="noopener">previous article</A>, we instantiate a <A href="#" target="_blank" rel="noopener">TranslateTransform</A> object (named <STRONG>dragTranslation</STRONG>) and we affect it to the <STRONG>RenderTransform</STRONG> property of the anchor. The goal is to apply a translation for every move during the drag &amp; drop action.</P> <PRE><CODE id="pragma-line-266" class="language-csharp hljs"><SPAN class="hljs-comment">// XAML Shapes manipulations</SPAN> <SPAN class="hljs-keyword">private</SPAN> TranslateTransform dragTranslation; <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Anchor_ManipulationStarted</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationStartedRoutedEventArgs e</SPAN>)</SPAN> { Ellipse anchor = (Ellipse)sender; <SPAN class="hljs-comment">// Initialize the transforms that will be used to manipulate the shape</SPAN> dragTranslation = <SPAN class="hljs-keyword">new</SPAN> TranslateTransform(); anchor.RenderTransform = dragTranslation; anchor.Fill = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Orange); } </CODE></PRE> <P>&nbsp;</P> <H3 id="provide-visual-feedback"><A id="pragma-line-282" target="_blank"></A>Provide visual feedback</H3> <P>The handler for the <EM>ManipulationDelta</EM> has to know if we are handling/moving the origin or the end of the line in order to be able to move the line correctly. That is why we have two different handlers but calling the same sub method. The third parameter is a boolean indicating if we hare modifying the origin (true) or not (false):</P> <PRE><CODE id="pragma-line-285" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Anchor_Origin_ManipulationDelta</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationDeltaRoutedEventArgs e</SPAN>)</SPAN> { AnchorManipulationDelta(sender, e, <SPAN class="hljs-literal">true</SPAN>); } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Anchor_End_ManipulationDelta</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationDeltaRoutedEventArgs e</SPAN>)</SPAN> { AnchorManipulationDelta(sender, e, <SPAN class="hljs-literal">false</SPAN>); } </CODE></PRE> <P>So, in the <EM>AnchorManipulationDelta</EM> method,</P> <UL id="pragma-line-300"> <LI id="pragma-line-300">We first modify the <STRONG>dragTranslation</STRONG> which acts on the anchor moves. This code effectively change the coordinates x and y of the anchor corresponding to the move of the mouse, pen or mouse.</LI> <LI id="pragma-line-301">After, we changes accordingly the position of either the origin or the end of the line.</LI> </UL> <PRE><CODE id="pragma-line-302" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">AnchorManipulationDelta</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationDeltaRoutedEventArgs e, <SPAN class="hljs-keyword">bool</SPAN> OriginofLine</SPAN>)</SPAN> { <SPAN class="hljs-keyword">double</SPAN> x = e.Delta.Translation.X; <SPAN class="hljs-keyword">double</SPAN> y = e.Delta.Translation.Y; dragTranslation.X += x; dragTranslation.Y += y; <SPAN class="hljs-keyword">if</SPAN> (OriginofLine) { activeLine.line.X1 += x; activeLine.line.Y1 += y; } <SPAN class="hljs-keyword">else</SPAN> { activeLine.line.X2 += x; activeLine.line.Y2 += y; } } </CODE></PRE> <P>&nbsp;</P> <H3 id="finish-the-manipulation"><A id="pragma-line-326" target="_blank"></A>Finish the manipulation</H3> <P>The same hack is used to identify if we did a move on the origin or on the end of the line.</P> <PRE><CODE id="pragma-line-328" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Anchor_Origin_ManipulationCompleted</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationCompletedRoutedEventArgs e</SPAN>)</SPAN> { AnchorManipulationCompleted(sender, e, <SPAN class="hljs-literal">true</SPAN>); } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Anchor_End_ManipulationCompleted</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationCompletedRoutedEventArgs e</SPAN>)</SPAN> { AnchorManipulationCompleted(sender, e, <SPAN class="hljs-literal">false</SPAN>); } </CODE></PRE> <P>The actions we have to perform for terminating the drag &amp; drop are:</P> <UL id="pragma-line-343"> <LI id="pragma-line-343">'Resetting' the translation applied to the anchor</LI> <LI id="pragma-line-344">Getting the total translation on the X axis and Y axis with the parameter <STRONG>ManipulationCompletedRoutedEventArgs.Cumulative.Translation</STRONG>.</LI> <LI id="pragma-line-345">Adding this translation to the original coordinates of either the origin or the end of the line in order to modify definitely its position.</LI> <LI id="pragma-line-346">Affecting the new position of the end of the line to the field <EM>activeLine</EM> that tracks the selected line.</LI> </UL> <PRE><CODE id="pragma-line-348" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">AnchorManipulationCompleted</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationCompletedRoutedEventArgs e, <SPAN class="hljs-keyword">bool</SPAN> OriginofLine</SPAN>)</SPAN> { Ellipse anchor = (Ellipse)sender; anchor.RenderTransform = <SPAN class="hljs-literal">null</SPAN>; <SPAN class="hljs-keyword">double</SPAN> x = e.Cumulative.Translation.X; <SPAN class="hljs-keyword">double</SPAN> y = e.Cumulative.Translation.Y; Canvas.SetLeft(anchor, Canvas.GetLeft(anchor) + x); Canvas.SetTop(anchor, Canvas.GetTop(anchor) + y); anchor.Fill = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Black); <SPAN class="hljs-keyword">if</SPAN> (OriginofLine) { activeLine.line.X1 = activeLine.initialX1 + x; activeLine.line.Y1 = activeLine.initialY1 + y; activeLine.initialX1 = activeLine.line.X1; activeLine.initialY1 = activeLine.line.Y1; } <SPAN class="hljs-keyword">else</SPAN> { activeLine.line.X2 = activeLine.initialX2 + x; activeLine.line.Y2 = activeLine.initialY2 + y; activeLine.initialX2 = activeLine.line.X2; activeLine.initialY2 = activeLine.line.Y2; } } </CODE></PRE> <P>Here we are!</P> <DIV id="tinyMceEditorSebastien Bovo_0" class="mceNonEditable lia-copypaste-placeholder">&nbsp;</DIV> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AdvancedManipulations.gif" style="width: 538px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/197028i3BAB744600FA15E0/image-size/large?v=v2&amp;px=999" role="button" title="AdvancedManipulations.gif" alt="Moving the origin or the end of the line with an anchor" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Moving the origin or the end of the line with an anchor</span></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="wrapping-up"><A id="pragma-line-386" target="_blank"></A>Wrapping up</H2> <P>The <A href="#" target="_blank" rel="noopener">Manipulation events</A> are really powerful and can be applied to any XAML Shapes. In this sample, we just create two circles like anchors at the ends of the line in order to give the ability to the user to manipulate the line. We could think of building a more sophisticated UI with different buttons/shapes to apply translations, rotations or any actions on any other shapes of the application!</P> <P>&nbsp;</P> <P>--</P> <P>&nbsp;</P> <P>The source is available on GitHub - <A href="#" target="_blank" rel="noopener">https://github.com/sbovo/UWP-Advanced-Inking</A></P> <P><A href="#" target="_blank" rel="noopener">@sbovo</A> for the AppConsult team.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="inking-series-articles"><A id="pragma-line-397" target="_blank"></A>Inking series' articles</H2> <P>This article is part of a series exploring concepts about inking and XAML Shapes. Here are all links:</P> <OL id="pragma-line-399"> <LI id="pragma-line-399"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">Use the UWP Inking platform as input for advanced scenarios</A></LI> <LI id="pragma-line-400"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/handling-zoom-in-inking-applications/ba-p/1354930" target="_blank" rel="noopener">Handling zoom in Inking applications</A></LI> <LI id="pragma-line-401"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/turning-to-the-dark-side-of-inking-unprocessedinput/ba-p/1364331" target="_blank" rel="noopener">Turning to the dark side of inking = UnprocessedInput</A></LI> <LI id="pragma-line-402"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899" target="_blank" rel="noopener">Free your mind: Start manipulating XAML Shapes</A></LI> <LI id="pragma-line-403"><STRONG>XAML Shapes manipulation level up</STRONG> ⇐ You are here</LI> </OL> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="references"><A id="pragma-line-405" target="_blank"></A>References</H2> <UL id="pragma-line-406"> <LI id="pragma-line-406">Source code of this article - <A href="#" target="_blank" rel="noopener">https://github.com/sbovo/UWP-Advanced-Inking</A></LI> <LI id="pragma-line-407">Ellipse Class - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Shapes.Ellipse</A></LI> <LI id="pragma-line-408">Manipulation events <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh465387(v=win.10)#using-manipulation-events</A></LI> <LI id="pragma-line-409">ManipulationMode <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationmode</A></LI> <LI id="pragma-line-410">UIElement.ManipulationStarted - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationstarted</A></LI> <LI id="pragma-line-411">UIElement.ManipulationDelta - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationdelta</A></LI> <LI id="pragma-line-412">UIElement.ManipulationCompleted - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationcompleted</A></LI> <LI id="pragma-line-413">TranslateTransform - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Media.TranslateTransform</A></LI> </UL> </DIV> Mon, 08 Jun 2020 12:31:03 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/xaml-shapes-manipulation-level-up/ba-p/1445405 Sebastien Bovo 2020-06-08T12:31:03Z Free your mind: Start manipulating XAML Shapes https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899 <DIV id="MainContent"> <P>You use the Inking platform to get inputs for lines/shapes' drawing. You may want to provide the users the ability to modify and move the shapes created. Ok, let's do it!</P> <P>Another time again, we start with the <A title="sample code" href="#" target="_blank" rel="noopener">sample code</A> related to the article <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">Use the UWP Inking platform as input for advanced scenarios</A>. This code is using the Inking platform to allow the user to draw lines and we replace the strokes drawn by XAML Lines.</P> <P>It is now time to jump to another step and allow direct manipulations of these lines like:</P> <UL id="pragma-line-6"> <LI id="pragma-line-6">Moving the entire line</LI> <LI id="pragma-line-7">Changing the size of the line by moving the origin or the end of the line</LI> </UL> <P>We plan to do it by using drag and drop with either mouse or touch.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="starting-code"><A id="pragma-line-11" target="_blank"></A>Starting code</H2> <P>For the explanations please refer to the article mentioned earlier. Here is the XAML and the code behind:</P> <PRE><CODE id="pragma-line-13" class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Grid</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">InkCanvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt;</SPAN> <SPAN class="hljs-comment">&lt;!-- Canvas for displaying the "recognized" XAML Shapes --&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Canvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"ShapesCanvas"</SPAN> /&gt;</SPAN> <SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Grid</SPAN>&gt;</SPAN> </CODE></PRE> <PRE><CODE id="pragma-line-22" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-title">MainPage</SPAN>()</SPAN> { <SPAN class="hljs-keyword">this</SPAN>.InitializeComponent(); <SPAN class="hljs-comment">// Initialize the InkCanvas</SPAN> inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch; <SPAN class="hljs-comment">// When the user finished to draw something on the InkCanvas</SPAN> inkCanvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected; } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">InkPresenter_StrokesCollected</SPAN>(<SPAN class="hljs-params"> Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args</SPAN>)</SPAN> { InkStroke stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Last(); <SPAN class="hljs-comment">// Action 1 = We use a function that we will implement just after to create the XAML Line</SPAN> Line line = ConvertStrokeToXAMLLine(stroke); <SPAN class="hljs-comment">// Action 2 = We add the Line in the second Canvas</SPAN> ShapesCanvas.Children.Add(line); <SPAN class="hljs-comment">// We delete the InkStroke from the InkCanvas</SPAN> stroke.Selected = <SPAN class="hljs-literal">true</SPAN>; inkCanvas.InkPresenter.StrokeContainer.DeleteSelected(); } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> Line <SPAN class="hljs-title">ConvertStrokeToXAMLLine</SPAN>(<SPAN class="hljs-params">InkStroke stroke</SPAN>)</SPAN> { <SPAN class="hljs-keyword">var</SPAN> line = <SPAN class="hljs-keyword">new</SPAN> Line(); line.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Green); line.StrokeThickness = <SPAN class="hljs-number">6</SPAN>; <SPAN class="hljs-comment">// The origin = (X1, Y1)</SPAN> line.X1 = stroke.GetInkPoints().First().Position.X; line.Y1 = stroke.GetInkPoints().First().Position.Y; <SPAN class="hljs-comment">// The end = (X2, Y2)</SPAN> line.X2 = stroke.GetInkPoints().Last().Position.X; line.Y2 = stroke.GetInkPoints().Last().Position.Y; <SPAN class="hljs-keyword">return</SPAN> line; } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="moving-a-line-by-drag-and-drop"><A id="pragma-line-70" target="_blank"></A>Moving a line by drag and drop</H2> <P>The goal here is to be able to touch/click on a line to do a drag and drop in order to move it on the surface. Of course, we will take care of the direct feedback to really see the line moving with the finger/mouse. We use the <A href="#" target="_blank" rel="noopener">Manipulation events</A>.</P> <P>First, with the <STRONG><A href="#" target="_blank" rel="noopener">ManipulationMode</A></STRONG> property, we can restrict for a certain type of manipulation. For example, only horizontal if the application needs it. In our sample, the line can be moved in any direction.</P> <PRE><CODE id="pragma-line-75" class="language-csharp hljs">line.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; </CODE></PRE> <P>After, we subscribe to the manipulation events:</P> <UL id="pragma-line-81"> <LI id="pragma-line-81"><A href="#" target="_blank" rel="noopener">ManipulationStarted</A></LI> <LI id="pragma-line-82"><A href="#" target="_blank" rel="noopener">ManipulationDelta</A></LI> <LI id="pragma-line-83"><A href="#" target="_blank" rel="noopener">ManipulationCompleted</A></LI> </UL> <PRE><CODE id="pragma-line-85" class="language-csharp hljs">line.ManipulationStarted += Line_ManipulationStarted; line.ManipulationDelta += Line_ManipulationDelta; line.ManipulationCompleted += Line_ManipulationCompleted; ; </CODE></PRE> <P>We have to handle these events for each line we create, the best place is in the <EM>ConvertStrokeToXAMLLine</EM> function:</P> <PRE><CODE id="pragma-line-94" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> Line <SPAN class="hljs-title">ConvertStrokeToXAMLLine</SPAN>(<SPAN class="hljs-params">InkStroke stroke</SPAN>)</SPAN> { <SPAN class="hljs-keyword">var</SPAN> line = <SPAN class="hljs-keyword">new</SPAN> Line(); <SPAN class="hljs-comment">// ... </SPAN> <SPAN class="hljs-comment">// ... </SPAN> <SPAN class="hljs-comment">// We use the manipulation events in order to move the shapes</SPAN> line.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; line.ManipulationStarted += Line_ManipulationStarted; line.ManipulationDelta += Line_ManipulationDelta; line.ManipulationCompleted += Line_ManipulationCompleted; ; <SPAN class="hljs-keyword">return</SPAN> line; } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="1-initiating-the-manipulation"><A id="pragma-line-114" target="_blank"></A>1. Initiating the manipulation</H2> <P>First, we create a field to keep track of the transformation applied to the line for which we ar doing the drag &amp; drop. We use a <A href="#" target="_blank" rel="noopener">TranslateTransform</A> object. It allows us to apply a translation transformation on the line in the 2D plan. That means, changing the x/y coordinates of the object.</P> <PRE><CODE id="pragma-line-116" class="language-csharp hljs"><SPAN class="hljs-comment">// XAML Shapes manipulations</SPAN> <SPAN class="hljs-keyword">private</SPAN> TranslateTransform dragTranslation; </CODE></PRE> <P>So, when we click or touch the line, we hit the <EM>Line_ManipulationStarted</EM> event handler.</P> <UL id="pragma-line-122"> <LI id="pragma-line-122">We first, get the object on which we clicked/touched. In our case, this object is a XAML Line but you manipulate more generic shapes, you will have adapt the code.</LI> <LI id="pragma-line-123">We instantiate our <STRONG>dragTranslation</STRONG> object and assign it to the <STRONG>RenderTransform</STRONG> property of the Line object. This way, the Line will be affected by the transformation if we modify it.</LI> <LI id="pragma-line-124">Just to give an extra visual feedback, we just change the color of the line.</LI> </UL> <PRE><CODE id="pragma-line-125" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Line_ManipulationStarted</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationStartedRoutedEventArgs e</SPAN>)</SPAN> { Line l = (Line)sender; <SPAN class="hljs-comment">// Initialize the Render transform that will be used to manipulate the shape</SPAN> dragTranslation = <SPAN class="hljs-keyword">new</SPAN> TranslateTransform(); l.RenderTransform = dragTranslation; l.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Orange); } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="2-giving-real-time-visual-feedback"><A id="pragma-line-136" target="_blank"></A>2. Giving real time visual feedback</H2> <P>When the user maintains the right click or the finger on the device, we hit the <EM>Line_ManipulationDelta</EM> event handler for every move. We can then provide the visual feedback we want, like display the coordinates of the move or simply effectively move the shape. This is easily done by affecting to <STRONG>dragTranslation</STRONG> object the X/Y translations that were performed since the previous event.</P> <BLOCKQUOTE id="pragma-line-140"> <P><STRONG>Note</STRONG>: By modifying the <STRONG>dragTranslation</STRONG> X and Y, we modify the coordinates of the Line we are holding because we previously affected <STRONG>dragTranslation</STRONG> (a <EM>TranslateTransform</EM> object) to the <STRONG>RenderTransform</STRONG> property of the Line.</P> </BLOCKQUOTE> <PRE><CODE id="pragma-line-142" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Line_ManipulationDelta</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationDeltaRoutedEventArgs e</SPAN>)</SPAN> { dragTranslation.X += e.Delta.Translation.X; dragTranslation.Y += e.Delta.Translation.Y; } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="3-ending-the-manipulation"><A id="pragma-line-151" target="_blank"></A>3. Ending the manipulation</H2> <P>Finally, when we release the click or the finger from the device surface, we hit the <EM>Line_ManipulationCompleted</EM> event handler. We can now terminate the 'drag &amp; drop':</P> <UL id="pragma-line-153"> <LI id="pragma-line-153">We first 'reset' the translation applied to the line because we will permanently modify the coordinates.</LI> <LI id="pragma-line-154">We get the total translation on the X axis and Y axis with the event handler parameter <STRONG>ManipulationCompletedRoutedEventArgs.Cumulative.Translation</STRONG>.</LI> <LI id="pragma-line-155">We add this translation to the original coordinates of the line object.</LI> <LI id="pragma-line-156">As usual,&nbsp;we modify the color of the line to give the feedback about the end of the 'drag &amp; drop'.</LI> </UL> <PRE><CODE id="pragma-line-158" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Line_ManipulationCompleted</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, ManipulationCompletedRoutedEventArgs e</SPAN>)</SPAN> { Line l = (Line)sender; l.RenderTransform = <SPAN class="hljs-literal">null</SPAN>; <SPAN class="hljs-comment">// Get the cumulative move</SPAN> <SPAN class="hljs-keyword">double</SPAN> x = e.Cumulative.Translation.X; <SPAN class="hljs-keyword">double</SPAN> y = e.Cumulative.Translation.Y; <SPAN class="hljs-comment">// Change the origin (X1,Y1) and the end of the line (X2,Y2)</SPAN> l.X1 += x; l.X2 += x; l.Y1 += y; l.Y2 += y; l.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Black); } </CODE></PRE> <P>Do you want to see the result in action? Here it is:</P> <DIV id="tinyMceEditorSebastien Bovo_0" class="mceNonEditable lia-copypaste-placeholder">&nbsp;</DIV> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="XAMLShapesManipulations.gif" style="width: 534px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196728i855038269D05DE2B/image-size/large?v=v2&amp;px=999" role="button" title="XAMLShapesManipulations.gif" alt="XAML Shapes Manipulations by drag and drop" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">XAML Shapes Manipulations by drag and drop</span></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="wrapping-up"><A id="pragma-line-182" target="_blank"></A>Wrapping up</H2> <P>This code starts to be interesting about the possibilities that we have to create a powerful drawing application. In the sample, we move by drag &amp; drop only lines but this code will apply to any <A href="#" target="_blank" rel="noopener">XAML Shapes</A> like Ellipse, Polygon, Rectangle or even a complex Path. Also, we just focus in this code to provide a live visual feedback for the shape's movement but you can imagine creating some control to snap to some region or to a grid and also display the coordinates of the actual position while moving.</P> <P>Free your mind and code!</P> <P>&nbsp;</P> <P>--</P> <P>&nbsp;</P> <P>All the source is on GitHub - <A href="#" target="_blank" rel="noopener">https://github.com/microsoft/Windows-AppConsult-Samples-UWP/</A></P> <P><A href="#" target="_blank" rel="noopener">@sbovo</A> for the AppConsult team.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="inking-series-articles"><A id="pragma-line-195" target="_blank"></A>Inking series' articles</H2> <P>This article is part of a series exploring concepts about inking and XAML Shapes. Here are all links:</P> <OL id="pragma-line-197"> <LI id="pragma-line-197"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">Use the UWP Inking platform as input for advanced scenarios</A></LI> <LI id="pragma-line-198"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/handling-zoom-in-inking-applications/ba-p/1354930" target="_blank" rel="noopener">Handling zoom in Inking applications</A></LI> <LI id="pragma-line-199"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/turning-to-the-dark-side-of-inking-unprocessedinput/ba-p/1364331" target="_blank" rel="noopener">Turning to the dark side of inking = UnprocessedInput</A></LI> <LI id="pragma-line-200"><STRONG>Free your mind: Start manipulating XAML Shapes</STRONG> ⇐ You are here</LI> <LI> <DIV id="MainContent"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/xaml-shapes-manipulation-level-up/ba-p/1445405" target="_blank" rel="noopener">XAML Shapes manipulation level up</A></P> </DIV> </LI> </OL> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="references"><A id="pragma-line-202" target="_blank"></A>References</H2> <UL id="pragma-line-203"> <LI id="pragma-line-203">Source code of this article - <A title="XAMLShapesManipulations source code" href="#" target="_blank" rel="noopener">https://github.com/microsoft/Windows-AppConsult-Samples-UWP/</A></LI> <LI>Manipulation events <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh465387(v=win.10)#using-manipulation-events</A></LI> <LI id="pragma-line-204">ManipulationMode <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationmode</A></LI> <LI id="pragma-line-205">UIElement.ManipulationStarted - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationstarted</A></LI> <LI id="pragma-line-206">UIElement.ManipulationDelta - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationdelta</A></LI> <LI id="pragma-line-207">UIElement.ManipulationCompleted - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.manipulationcompleted</A></LI> <LI id="pragma-line-208">TranslateTransform - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Media.TranslateTransform</A></LI> </UL> </DIV> Sat, 06 Jun 2020 16:06:20 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899 Sebastien Bovo 2020-06-06T16:06:20Z Signing a MSIX package with Azure Key Vault https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/signing-a-msix-package-with-azure-key-vault/ba-p/1436154 <P><SPAN>Signing is one of the most critical aspects when working with MSIX packages. One of the key goals of MSIX as deployment technology is to ensure safety and trust and signing is one of the ways to achieve it. Thanks to signing, we are sure that the application we're about to install is coming from a trusted publisher and not from a potentially malicious developer. Certification authorities (either enterprise or public ones) have a rigorous background check process, which makes sure that when you request a certificate under a name or a brand, you're indeed the person or the company you claim to be.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="certificate.png" style="width: 699px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196145iC2A09D2619EF1882/image-size/large?v=v2&amp;px=999" role="button" title="certificate.png" alt="certificate.png" /></span></SPAN></P> <P>For the same reason, certificates must be stored safely to avoid any identity theft. If we would expose the private key of the certificate on a public repository, for example, other developers could reuse it and sign other applications with our identity.</P> <P>&nbsp;</P> <P>As such, especially when we want to implement a CI/CD pipeline to continuously build and deploy our desktop applications, we need to find the right balance between safety and convenience. We want to make it easy to sign our packages with our certificates as part of the process; at the same time, we must do it without compromising the private key.</P> <P>&nbsp;</P> <P>There are various options to achieve this goal. In this post, we're going to focus on<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Azure Key Vault</A>, a service provided by Azure which allows to store in a safe way keys, secrets and certificates. Azure Key Vault is a critical service for many scenarios: as developers, for example, we can use it to store the connection strings to our database; or the secrets to connect to a 3rd part service, like a protected API. Thanks to the APIs offered by Azure Key Vault, we can retrieve the protected information at runtime, without having to expose them in the source code or in a configuration file. Among the various features offered by Azure Key Vault, we can use it also to store certificates. We can use it to acquire, generate and renew certificates from a public authority, relieving us from all the troubles of certificates management.</P> <P>&nbsp;</P> <P>In this post we're going to see how we can store on Azure Key Vault a certificate we own and then use it to sign a MSIX package using an open source tool called<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Azure Sign Tool,</A>&nbsp;developed by <A href="#" target="_self">Kevin Jones</A>. In the end, we're going to see how we can integrate it in our CI/CD pipeline on Azure DevOps or GitHub.</P> <P>&nbsp;</P> <H3 id="setup-azure-key-vault">Setup Azure Key Vault</H3> <P>As first step, you must login to the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Azure Portal</A><SPAN>&nbsp;</SPAN>with your Azure credentials. Click on the<SPAN>&nbsp;</SPAN><STRONG>Create a resource</STRONG><SPAN>&nbsp;</SPAN>button and, using the internal search, look for<SPAN>&nbsp;</SPAN><STRONG>Key Vault</STRONG>:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeyVault.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196146i7C84FA3FA6A0C336/image-size/large?v=v2&amp;px=999" role="button" title="KeyVault.png" alt="KeyVault.png" /></span></P> <P>Click on<SPAN>&nbsp;</SPAN><STRONG>Create</STRONG><SPAN>&nbsp;</SPAN>to kick start the provisioning process. There are only a few required settings to setup:</P> <P>&nbsp;</P> <UL> <LI id="pragma-line-21">Name</LI> <LI id="pragma-line-22">Region</LI> <LI id="pragma-line-23">Pricing tier: for this post I'm going to use the default one. However, if you really care about security, you should consider the Premium tier, which supports to store secret and keys backed by Hardware Security Modules.</LI> <LI id="pragma-line-24">Retention period (leave the default value of 90 days)</LI> </UL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KeyVaultSetup.png" style="width: 776px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196147i6BC7104C0861B5F2/image-size/large?v=v2&amp;px=999" role="button" title="KeyVaultSetup.png" alt="KeyVaultSetup.png" /></span></P> <P><SPAN>Once you're done, click&nbsp;</SPAN><STRONG>Review + create</STRONG><SPAN>, followed by the&nbsp;</SPAN><STRONG>Create</STRONG><SPAN>&nbsp;button to complete the deployment. Once it's completed, go to the resource. We're now ready to import our certificate.</SPAN></P> <P>&nbsp;</P> <H3 id="create-or-import-the-certificate">Create or import the certificate</H3> <P>Before you start you need to decide which kind of certificate you're going to use. Ideally, you should have acquired it from a certificate authority like<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">DigiCert</A>. These certificates are the best to use for public distribution, since they are automatically trusted by Windows 10. As such, the user won't have to do anything special to install your application. If you don't have one and you aren't willing to buy one for the moment, you can also generate a self-signing certificate directly through the portal. It isn't the ideal solution for publicly distributing an application since these certificates can't validate the identify of the person / company and, as such, they aren't implicitly trusted by Windows. The user will need to manually trust the certificate, which requires administrator rights.</P> <P>&nbsp;</P> <P>However, self-signing certificates are good enough for testing, so let's use this approach. In the Key Vault you have just created move to<SPAN>&nbsp;</SPAN><STRONG>Certificate</STRONG><SPAN>&nbsp;</SPAN>and click on<SPAN>&nbsp;</SPAN><STRONG>Generate/Import</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NewCertificate.png" style="width: 939px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196148iAB1027811613CD8A/image-size/large?v=v2&amp;px=999" role="button" title="NewCertificate.png" alt="NewCertificate.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Let's start the process to generate a new self-signed certificate. This is the setup screen:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="CreateCertificate.png" style="width: 676px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196149i5EAD71C42EEE0290/image-size/large?v=v2&amp;px=999" role="button" title="CreateCertificate.png" alt="CreateCertificate.png" /></span></SPAN></P> <P>These are the values to configure:</P> <UL> <LI><STRONG>Method or Certificate creation</STRONG>: since we're creating a new certificate, leave<SPAN>&nbsp;</SPAN><STRONG>Generate</STRONG>.</LI> <LI><STRONG>Certificate Name</STRONG>: this name identifies your certificate.</LI> <LI><STRONG>Type or Certificate Authority (CA)</STRONG>: leave self-signed certificate. Eventually, you can change this option to start the process to directly purchase a certificate from a public CA and store it in Key Vault.</LI> <LI><STRONG>Subject</STRONG>: this field is very important because it must match the Publisher you're going to use in the manifest of your MSIX package.</LI> </UL> <P>Leave all the other settings with the default value. However, there's one last important section to setup:<SPAN>&nbsp;</SPAN><STRONG>Advanced Policy Configuration</STRONG>. If we click on it, we will have access to various advanced options for the certificate. The most important one is<SPAN>&nbsp;</SPAN><STRONG>Ektended Key Usage (EKUs)</STRONG>, which specifies the various use cases supported by the certificate. As per<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">the documentation</A>, in order to use a certificate to sign a MSIX package we need to enable the following EKUs:</P> <P>&nbsp;</P> <UL> <LI>1.3.6.1.5.5.7.3.3 indicates that the certificate is valid for code signing.</LI> <LI>1.3.6.1.4.1.311.10.3.13 indicates that the certificate respects lifetime signing.</LI> </UL> <P><SPAN>As such, you need to replace the existing values with these two ones, separated by a comma. Another setting that you may want to configure, in order to get better security, is to set the&nbsp;</SPAN><STRONG>Exportable Private Key</STRONG><SPAN>&nbsp;to&nbsp;</SPAN><STRONG>No</STRONG><SPAN>. This way, even if you store the certificate locally, you won't be able to export the private key in any way.</SPAN></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="PolicyConfiguration.png" style="width: 403px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196206i1D43B694BFE71E8F/image-size/large?v=v2&amp;px=999" role="button" title="PolicyConfiguration.png" alt="PolicyConfiguration.png" /></span></P> <P>&nbsp;</P> <P><SPAN>At the end of the process, press&nbsp;</SPAN><STRONG>Create</STRONG><SPAN>. The certificate will be displayed initially in the&nbsp;</SPAN><STRONG>In progress, failed or cancelled</STRONG><SPAN>&nbsp;section but, if you refresh after a few seconds, you will see it appearing in the&nbsp;</SPAN><STRONG>Completed</STRONG><SPAN>&nbsp;section.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="CertificateCreated.png" style="width: 887px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196152i28D02D03849B3772/image-size/large?v=v2&amp;px=999" role="button" title="CertificateCreated.png" alt="CertificateCreated.png" /></span></SPAN></P> <P>If, instead, you already have a certificate you want to use, you'll just need to choose<SPAN>&nbsp;</SPAN><STRONG>Import</STRONG><SPAN>&nbsp;</SPAN>in the<SPAN>&nbsp;</SPAN><STRONG>Method of Certificate creation</STRONG><SPAN>&nbsp;</SPAN>dropdown when you start the process to generate or import a certificate.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ImportCertificate.png" style="width: 642px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196155i9EE3501C90338949/image-size/large?v=v2&amp;px=999" role="button" title="ImportCertificate.png" alt="ImportCertificate.png" /></span></P> <P><SPAN>In this case the procedure will be simpler, since you'll just need to assign a name to the certificate, browse your computer to find the right PFX file and provide the password.</SPAN></P> <H3 id="register-a-new-application">Register a new Azure application</H3> <P>In order to access to our Key Vault, we need a way to connect to it through protected credentials. The way to do that is by registering a new application in Azure, which will have access to our certificate. Then, the Azure Sign Tool utility will connect to the Key Vault through the credentials associated to our application.</P> <P>&nbsp;</P> <P>Let's start the process. Go back to the home page of your Azure portal and choose<SPAN>&nbsp;</SPAN><STRONG>Azure Active Directory</STRONG>, then move to<SPAN>&nbsp;</SPAN><STRONG>App registrations</STRONG>. Click on<SPAN>&nbsp;</SPAN><STRONG>New registration</STRONG><SPAN>&nbsp;</SPAN>to start the process.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NewRegistration.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196156iBEE9B9B3A29813AD/image-size/large?v=v2&amp;px=999" role="button" title="NewRegistration.png" alt="NewRegistration.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Give a name to your application, then leave the default settings. We aren't going to use this application to enable the Microsoft Identity platform, so we can use the standard configuration.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RegisterAnApplication.png" style="width: 824px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196157i48808A260675C83C/image-size/large?v=v2&amp;px=999" role="button" title="RegisterAnApplication.png" alt="RegisterAnApplication.png" /></span></SPAN></P> <P><SPAN>The next step is to treat the application as a public client, since we are in a scenario where a redirect URI is not used (again, we aren't using this registration to enable authentication in an web or desktop application to leverage features like SSO or Microsoft Graph). Move to the&nbsp;</SPAN><STRONG>Authentication</STRONG><SPAN>&nbsp;section and, under&nbsp;</SPAN><STRONG>Advanced settings</STRONG><SPAN>, move the switch&nbsp;</SPAN><STRONG>Treat application as a public client</STRONG><SPAN>&nbsp;to&nbsp;</SPAN><STRONG>Yes</STRONG><SPAN>.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AdvancedSettings.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196158iC103BEA744A48801/image-size/large?v=v2&amp;px=999" role="button" title="AdvancedSettings.png" alt="AdvancedSettings.png" /></span></SPAN></P> <P><SPAN>The last step is to create a client secret, which is the password we'll need to authenticate from the Azure SignTool. Move to the&nbsp;<STRONG>Certificates &amp; secrets</STRONG>&nbsp;section, then click on&nbsp;<STRONG>New client secret</STRONG>.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AddAClientSecret.png" style="width: 913px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196159i8284BD3A163F65F9/image-size/large?v=v2&amp;px=999" role="button" title="AddAClientSecret.png" alt="AddAClientSecret.png" /></span></SPAN></P> <P>Once you have pressed the<SPAN>&nbsp;</SPAN><STRONG>Add</STRONG><SPAN>&nbsp;</SPAN>button, you will be redirected back to the main page, where the secret will be listed together with its value. Make sure to copy it and to store it somewhere safe. You won't be able to retrieve it again. As soon as you refresh the page, the secret will be masked and there won't be any way to reveal it. You will be forced to generate a new secret.</P> <P>&nbsp;</P> <P>There's one last information we need to store together with the client secret: the application identifier. Go back to the home of the application (by clicking on<SPAN>&nbsp;</SPAN><STRONG>Overview</STRONG>) and, in the upper section, look for the value<SPAN>&nbsp;</SPAN><STRONG>Application (client) ID</STRONG>:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ApplicationId.png" style="width: 692px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196160iC3D6655AA2B97CB0/image-size/large?v=v2&amp;px=999" role="button" title="ApplicationId.png" alt="ApplicationId.png" /></span></P> <P><SPAN>Make a note of it, since we're going to need it later. That's it for the app configuration.</SPAN></P> <P>&nbsp;</P> <H3 id="enable-access-to-key-vault">Enable access to Key Vault</H3> <P>Now go back to the Key Vault you have previously created and move to the<SPAN>&nbsp;</SPAN><STRONG>Access policies</STRONG><SPAN>&nbsp;</SPAN>section. This is the place where we can enable our new application to access to the information stored in the Key Vault.&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AccessPolicies.png" style="width: 867px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196161iD01FAA9CA899B61D/image-size/large?v=v2&amp;px=999" role="button" title="AccessPolicies.png" alt="AccessPolicies.png" /></span></P> <P>&nbsp;</P> <P>Click on<SPAN>&nbsp;</SPAN><STRONG>Add Access Policy</STRONG><SPAN>&nbsp;</SPAN>to start the wizard to setup the access. The tool supports choosing one of the available templates to define the permissions we want to grant but, in our scenario, no one of them is a good fit. We can manually set the right permission using the other dropdowns. Specifically, for our scenario we need to:</P> <P>&nbsp;</P> <UL> <LI>Under<SPAN>&nbsp;</SPAN><STRONG>Key permissions</STRONG>, enable the<SPAN>&nbsp;</SPAN><STRONG>Sign</STRONG><SPAN>&nbsp;</SPAN>option.</LI> <LI>Under<SPAN>&nbsp;</SPAN><STRONG>Certificate permissions</STRONG>, enable the<SPAN>&nbsp;</SPAN><STRONG>Get</STRONG><SPAN>&nbsp;</SPAN>option.</LI> </UL> <P>These permissions are enough for Azure SignTool: the second one will enable it to download the certificate, the first one to use it for signing the MSIX package.</P> <P>&nbsp;</P> <P>The last important step is to specify which application is going to access to this policy. Click on<SPAN>&nbsp;</SPAN><STRONG>Select principal</STRONG><SPAN>&nbsp;</SPAN>and, using the internal search, look for the Azure application you have created in the previous step. In my case, for example, it's called<SPAN>&nbsp;</SPAN><STRONG>SignToolForContoso</STRONG>:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="SignToolForContoso.png" style="width: 304px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196162i3B0718D4645D4DB2/image-size/large?v=v2&amp;px=999" role="button" title="SignToolForContoso.png" alt="SignToolForContoso.png" /></span></P> <P>Once you have found it, click on it and then press<SPAN>&nbsp;</SPAN><STRONG>Select</STRONG><SPAN>&nbsp;</SPAN>at the end of the blade.</P> <P>This is how the policy should look like:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AddAccessPolicy.png" style="width: 502px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196163i047268F0FF139CD1/image-size/large?v=v2&amp;px=999" role="button" title="AddAccessPolicy.png" alt="AddAccessPolicy.png" /></span></P> <P><SPAN>Press&nbsp;<STRONG>Add</STRONG>&nbsp;and, when you're back to the main Access policies page, hit&nbsp;<STRONG>Save</STRONG>.</SPAN></P> <P>&nbsp;</P> <H3 id="sign-the-msix-package">Sign the MSIX package</H3> <P>We have completed the configuration on the Azure side. Now we can start the process of signing the MSIX package. As first step, we need to acquire the Azure SignTool utility. Azure SignTool works like the signtool included in the Windows 10 SDK, with the difference that instead of using a local certificate, it can leverage the ones we have stored in Azure Key Vault.</P> <P>The easiest way to acquire it is through the .NET Core CLI, since it's available as a .NET Core Global Tool. Make sure to have the latest<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">.NET Core SDK</A><SPAN>&nbsp;</SPAN>installed on your machine, then open a terminal and run the following command:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">dotnet tool install --global AzureSignTool --version <SPAN class="hljs-number">2.0</SPAN>.<SPAN class="hljs-number">17</SPAN> </CODE></PRE> <P>Once the operation has been completed, you will be able to run the command simply by typing<SPAN>&nbsp;</SPAN><CODE>AzureSignTool</CODE><SPAN>&nbsp;</SPAN>from any location. Now you will be able to sign your MSIX packages by running the following command:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">AzureSignTool sign -kvu <SPAN class="hljs-string">"https://contosoexpenses-blog.vault.azure.net/"</SPAN> -kvi <SPAN class="hljs-string">"64fae35e-cb84-4b9f-86eb-5170d169316d"</SPAN> -kvs <SPAN class="hljs-string">"this-is-the-secret"</SPAN> -kvc <SPAN class="hljs-string">"AppConsult"</SPAN> -tr http://timestamp.digicert.com -v .\MyContosoApp.Package_1.<SPAN class="hljs-number">0.1</SPAN>.<SPAN class="hljs-number">0</SPAN>_x86.msix </CODE></PRE> <P>Let's see in details all the parameters we have configured:</P> <UL> <LI> <P><CODE>kvu</CODE><SPAN>&nbsp;</SPAN>is the URL of your Key Vault. You can find it in the main page of the service in the Azure portal, under<SPAN>&nbsp;</SPAN><STRONG>DNS Name</STRONG>.</P> </LI> </UL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="DNSName.png" style="width: 849px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196164i40F054A49082364C/image-size/large?v=v2&amp;px=999" role="button" title="DNSName.png" alt="DNSName.png" /></span></P> <UL> <LI> <P><CODE>kvi</CODE><SPAN>&nbsp;</SPAN>is the application id of the Azure app we have previously registered.</P> </LI> <LI> <P><CODE>kvs</CODE><SPAN>&nbsp;</SPAN>is the client secret we have previously generated.</P> </LI> <LI> <P><CODE>kvc</CODE><SPAN>&nbsp;</SPAN>is the friendly name of our certificate that we have chosen during the creation / import process.</P> </LI> <LI> <P><CODE>tr</CODE><SPAN>&nbsp;</SPAN>is the URL of a timestamp server. By using this option, we can enable our signing to work even after the certificate has expired.</P> </LI> <LI> <P><CODE>v</CODE><SPAN>&nbsp;</SPAN>is the path of the MSIX package we want to sign.</P> </LI> </UL> <P><SPAN>That's it. If we did everything correct, this is the output we should see in the terminal:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Terminal.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196165iC592ADA11FF55974/image-size/large?v=v2&amp;px=999" role="button" title="Terminal.png" alt="Terminal.png" /></span></SPAN></P> <P>&nbsp;</P> <P><STRONG>Please note!&nbsp;</STRONG>Be aware that future releases of Azure SignTool will require also a new parameter to do the signing: <STRONG>-kvt</STRONG>, which is the&nbsp;<STRONG>Directory (tenant) ID&nbsp;</STRONG>that you can find in the Azure App registration page.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="TenantId.png" style="width: 774px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196407iC3E304BD64A75603/image-size/large?v=v2&amp;px=999" role="button" title="TenantId.png" alt="TenantId.png" /></span></P> <P>This is a new requirement enforced by the most recent versions of the Azure SDK leveraged by the tool. The current version is still using the old one so, until the new version will be out, the&nbsp;<STRONG>-kt</STRONG> parameter won't be needed (actually it will break the tool, because the current version doesn't recognize it as a valid parameter).</P> <H3 id="what-if-something-goes-wrong">What if something goes wrong?</H3> <P>There are two main reasons that could lead the Azure SignTool to fail:</P> <OL> <OL> <LI> <P>You didn't follow all the steps in the right way. It's enough to miss one of the steps (like forgetting to set the Azure app as public client or missing one of the permissions to the access policy) to move to an unsupported state.</P> </LI> <LI> <P>You didn't set the Publisher in the manifest of your MSIX package with the same subject you have selected for the certificate. For example, in my case I have created the certificate with subject<SPAN>&nbsp;</SPAN><STRONG>CN=AppConsult</STRONG>, so the<SPAN>&nbsp;</SPAN><CODE>Identity</CODE><SPAN>&nbsp;</SPAN>section of my manifest looks like this:</P> <P>&nbsp;</P> <PRE><CODE class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Identity</SPAN> <SPAN class="hljs-attr">Name</SPAN>=<SPAN class="hljs-string">"MyContosoApp"</SPAN> <SPAN class="hljs-attr">Publisher</SPAN>=<SPAN class="hljs-string">"CN=AppConsult"</SPAN> <SPAN class="hljs-attr">Version</SPAN>=<SPAN class="hljs-string">"1.0.1.0"</SPAN> <SPAN class="hljs-attr">ProcessorArchitecture</SPAN>=<SPAN class="hljs-string">"x86"</SPAN> /&gt;</SPAN></CODE></PRE> </LI> </OL> </OL> <H3 id="installing-the-application">Installing the application</H3> <P>If you did everything correctly, when you double click on the MSIX package Windows will list it as a trusted package and you will be able to go on and install it:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="TrustedPublisher.png" style="width: 650px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196166i7217DA0C2E9F14F6/image-size/large?v=v2&amp;px=999" role="button" title="TrustedPublisher.png" alt="TrustedPublisher.png" /></span></P> <P><SPAN>If you see, instead, an error like the one below, it's simply because the certificate is not trusted. Remember that, if you're using a self-signed certificate like I did so far, it isn't automatically trusted by Windows.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NotTrusted.png" style="width: 650px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196167iD2172230E1D27D1B/image-size/large?v=v2&amp;px=999" role="button" title="NotTrusted.png" alt="NotTrusted.png" /></span></SPAN></P> <P><SPAN>As such, you will need to right click on the MSIX package in File Explorer, click on&nbsp;</SPAN><STRONG>Properties</STRONG><SPAN>, choose&nbsp;</SPAN><STRONG>Digital Signatures</STRONG><SPAN>&nbsp;and, from there, start the process to install the certificate in the&nbsp;</SPAN><STRONG>Trusted People</STRONG><SPAN>&nbsp;store.</SPAN></P> <P>&nbsp;</P> <H3 id="using-azure-key-vault-as-part-of-your-cicd-pipeline">Using Azure Key Vault as part of your CI/CD pipeline</H3> <P>So far we have seen how to manually use the Azure SignTool utility. However, the scenario in which can be really helpful is in a CI/CD pipeline, where we need to automate the signing operation but, at the same time, protect the certificate. Regardless if we have built our CI/CD pipeline in Azure DevOps or GitHub, the fundamentals of the operations are the same:</P> <P>&nbsp;</P> <OL> <LI>We need to install the Azure SignTool on the hosted agent</LI> <LI>We need to run a script to sign the package using the same command we have just used manually from the terminal</LI> </OL> <P>The only difference, based on the platform you're using, is the way you're going to use to protect the sensitive information required by the tool, like the application id and the client secret. We can't expose them publicly in the pipeline, otherwise we are back to square one. Let's see how to leverage Azure Key Vault in two common platforms: Azure DevOps and GitHub Actions.</P> <P>&nbsp;</P> <H3 id="using-azure-key-vault-in-azure-devops">Using Azure Key Vault in Azure DevOps</H3> <P>If you don't already have a CI/CD pipeline for a desktop app on Azure DevOps, you can learn how to create one<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">in the official documentation</A><SPAN>&nbsp;</SPAN>or in<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">the last chapter of my free e-book</A>.</P> <P>When I use this platform, I prefer to leverage a Release pipeline to take care of the signing and deployment of the MSIX package. This way, I can define multiple deployment stages. For example, this is how my Release pipeline looks like:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ReleasePipeline.png" style="width: 969px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196168i5A84DE53411C0A78/image-size/large?v=v2&amp;px=999" role="button" title="ReleasePipeline.png" alt="ReleasePipeline.png" /></span></P> <P>&nbsp;</P> <P><SPAN>I have two stages: one for deployment in a testing environment and one in production environment. The deployment to the testing environment kicks in automatically whenever there's a new MSIX package (which is produced, instead, by a regular pipeline). Let's see how one of my stages look like:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="pipeline.png" style="width: 516px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196169iB032506A0AA749CF/image-size/large?v=v2&amp;px=999" role="button" title="pipeline.png" alt="pipeline.png" /></span></SPAN></P> <P>&nbsp;</P> <P>In this pipeline you can see the two actions we need to perform: install Azure SignTool and sign the package with it. Let's configure it. As first step, we need to use the Variables feature provided by Azure DevOps to safely store the credentials to access to Key Vault. Click on<SPAN>&nbsp;</SPAN><STRONG>Variables</STRONG>, make sure you are in the<SPAN>&nbsp;</SPAN><STRONG>Pipeline variables</STRONG><SPAN>&nbsp;</SPAN>section and then add a new variable for each sensitive information:</P> <P>&nbsp;</P> <UL> <LI>The Azure Key Vault URL</LI> <LI>The Azure Key Vault name</LI> <LI>The application identifier</LI> <LI>The client secret</LI> </UL> <P>After you have created them, make sure to click on the lock icon near each variable. This option will protect them, making sure that no one else (including yourself) will be able to see the value.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Variables.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196170i9845A52F8C3E34E6/image-size/large?v=v2&amp;px=999" role="button" title="Variables.png" alt="Variables.png" /></span></P> <P>&nbsp;</P> <P><SPAN>As second step, click on&nbsp;</SPAN><STRONG>Agent job</STRONG><SPAN>&nbsp;and make sure it's running on&nbsp;</SPAN><STRONG>windows-2019</STRONG><SPAN>. Azure SignTool is a .NET Core Global Tool, but it leverages specific Windows APIs to perform the signing. Then let's add the first task, by clicking on the + sign near the Agent job. Look for the task called&nbsp;</SPAN><STRONG>.NET Core</STRONG><SPAN>&nbsp;and click on&nbsp;</SPAN><STRONG>Add</STRONG><SPAN>:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="netcoretask.png" style="width: 463px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196171iCED718331530EC96/image-size/large?v=v2&amp;px=999" role="button" title="netcoretask.png" alt="netcoretask.png" /></span></SPAN></P> <P>&nbsp;</P> <P>Now let's configure the task to install Azure SignTool:</P> <UL> <LI><STRONG>Display name</STRONG><SPAN>&nbsp;</SPAN>will be used as a label for the step, so choose the name you prefer (in my case, I used<SPAN>&nbsp;</SPAN><STRONG>Install Azure SignTool</STRONG>).</LI> <LI><STRONG>Command</STRONG><SPAN>&nbsp;</SPAN>is the .NET Core command we want to perform. Choose<SPAN>&nbsp;</SPAN><STRONG>custom</STRONG>, since<SPAN>&nbsp;</SPAN><STRONG>tool</STRONG><SPAN>&nbsp;</SPAN>isn't available in the dropdown.</LI> <LI><STRONG>Custom command</STRONG><SPAN>&nbsp;</SPAN>is the .NET CLI command we want to run. Set it to<SPAN>&nbsp;</SPAN><STRONG>tool</STRONG>.</LI> <LI><STRONG>Arguments</STRONG><SPAN>&nbsp;</SPAN>is where we define all command parameters. Set it to<SPAN>&nbsp;</SPAN><STRONG>install --global AzureSignTool --version 2.0.17</STRONG>.</LI> </UL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="InstallAzureSignTool.png" style="width: 438px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196172iF3EF0F8965DEBF25/image-size/large?v=v2&amp;px=999" role="button" title="InstallAzureSignTool.png" alt="InstallAzureSignTool.png" /></span></P> <P>In case you're using a multi-stage deployment pipeline based on YAML instead of a traditional release pipeline, this is the equivalent YAML of the task we have just created:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">steps:</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">DotNetCoreCLI@2</SPAN> <SPAN class="hljs-attr"> displayName:</SPAN> <SPAN class="hljs-string">'Install Azure SignTool'</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> command:</SPAN> <SPAN class="hljs-string">custom</SPAN> <SPAN class="hljs-attr"> custom:</SPAN> <SPAN class="hljs-string">tool</SPAN> <SPAN class="hljs-attr"> arguments:</SPAN> <SPAN class="hljs-string">'install --global AzureSignTool --version 2.0.17'</SPAN> </CODE></PRE> <P>The next step is to sign the package. Click again on + near Agent job and, this time, add a PowerShell task. Give it a name in the<SPAN>&nbsp;</SPAN><STRONG>Display name</STRONG><SPAN>&nbsp;</SPAN>field and, in the<SPAN>&nbsp;</SPAN><STRONG>Type</STRONG><SPAN>&nbsp;</SPAN>option, choose<SPAN>&nbsp;</SPAN><STRONG>Inline</STRONG>. Then copy and paste the following command in the<SPAN>&nbsp;</SPAN><STRONG>Script</STRONG><SPAN>&nbsp;</SPAN>field:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">&amp; AzureSignTool sign -kvu $(AzureKeyVaultUrl) -kvi $(ClientId) -kvs $(ClientPassword) -kvc $(AzureKeyVaultName)-tr http://timestamp.digicert.com -v <SPAN class="hljs-string">"$(System.DefaultWorkingDirectory)\_ContosoExpenses-Basic\CD\ContosoExpenses.Package_$(Build.BuildNumber).0_Test\ContosoExpenses.Package_$(Build.BuildNumber).0_x86.msixbundle"</SPAN> </CODE></PRE> <P>We're launching the same exact command we have previously manually launched on our computer. The only difference is that the various parameters which contains sensitive information are set using the variables we have previously defined, by leveraging the Azure DevOps syntax<SPAN>&nbsp;</SPAN><CODE>$(variable-name)</CODE>. The<SPAN>&nbsp;</SPAN><CODE>-v</CODE><SPAN>&nbsp;</SPAN>parameter is the only one you will have to change, since it must point to the path of your MSIX file in your artifacts folder.</P> <P>This is the equivalent YAML for a multi-stage pipeline:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">steps:</SPAN> <SPAN class="hljs-attr">- powershell:</SPAN> <SPAN class="hljs-string">'&amp; AzureSignTool sign -kvu $(AzureKeyVaultUrl) -kvi $(ClientId) -kvs $(ClientPassword) -kvc $(AzureKeyVaultName)-tr http://timestamp.digicert.com -v "$(System.DefaultWorkingDirectory)\_ContosoExpenses-Basic\CD\ContosoExpenses.Package_$(Build.BuildNumber).0_Test\ContosoExpenses.Package_$(Build.BuildNumber).0_x86.msixbundle"'</SPAN> <SPAN class="hljs-attr"> displayName:</SPAN> <SPAN class="hljs-string">'Sign the package'</SPAN> </CODE></PRE> <P>That's it. Now that you have a signed MSIX package, you can add another task to take care of the deployment, based on your infrastructure. In my case, for example, I'm copying the file on a blob storage on Azure.</P> <P>&nbsp;</P> <H3 id="using-azure-keyvault-with-github-actions">Using Azure KeyVault with GitHub Actions</H3> <P>GitHub Actions is gaining lot of popularity and the .NET team has done a great job to support Windows developers who want to use it to build and deploy their desktop applications.<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Edward Skrod</A><SPAN>&nbsp;</SPAN>has prepared<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">a detailed step-by-step guide</A><SPAN>&nbsp;</SPAN>and also helped to put together<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">a workflow template</A><SPAN>&nbsp;</SPAN>that you can use as a starting point.</P> <P>&nbsp;</P> <P>Compared to Azure DevOps, currently GitHub doesn't support a Release management story, but you can create multi-stage pipelines which performs the tasks in different stages. For example, you can have a single YAML file which describes both a build and a deployment task. Let's see how to add to our workflow the tasks we need to install and use Azure SignTool.</P> <P>&nbsp;</P> <P>As first step, like we did on Azure DevOps, we need to safely store the credentials. GitHub calls them<SPAN>&nbsp;</SPAN><STRONG>secrets</STRONG><SPAN>&nbsp;</SPAN>and they can be added in the settings of your repository. Once you're on your GitHub repository, click on<SPAN>&nbsp;</SPAN><STRONG>Settings</STRONG><SPAN>&nbsp;</SPAN>then move to<SPAN>&nbsp;</SPAN><STRONG>Secrets</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Secrets.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196173i990E6EE7D339A672/image-size/large?v=v2&amp;px=999" role="button" title="Secrets.png" alt="Secrets.png" /></span></P> <P>&nbsp;</P> <P>From here, you just need to click on<SPAN>&nbsp;</SPAN><STRONG>New secret</STRONG><SPAN>&nbsp;</SPAN>and, like you did with Azure DevOps, add a new secret for each protected information: the Key Vault URL, the Key Vault name, the application id and the client secret. The only difference is that we won't have to take any extra step to protect them since secrets are already stored in a safe way. You'll be able only to update or remove a secret, no one will see its content.</P> <P>&nbsp;</P> <P>Now you can move to your workflow and add the tasks we need to do the signing. You will have to add them after that the MSIX package has been generated so, ideally, after the Build task.</P> <P>Let's start from the task to install Azure SignTool:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">- name:</SPAN> <SPAN class="hljs-string">Install</SPAN> <SPAN class="hljs-string">AzureSignTool</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">dotnet</SPAN> <SPAN class="hljs-string">tool</SPAN> <SPAN class="hljs-string">install</SPAN> <SPAN class="hljs-bullet">--global</SPAN> <SPAN class="hljs-string">AzureSignTool</SPAN> <SPAN class="hljs-bullet">--version</SPAN> <SPAN class="hljs-number">2.0</SPAN><SPAN class="hljs-number">.17</SPAN> </CODE></PRE> <P>Nothing special, it's just a script task which will execute the same command we have used before to install Azure SignTool on our PC. Let's move now to the singing task:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-attr">- name:</SPAN> <SPAN class="hljs-string">Sign</SPAN> <SPAN class="hljs-string">package</SPAN> <SPAN class="hljs-attr"> run:</SPAN> <SPAN class="hljs-string">| Get-ChildItem -recurse -Include *.msix | ForEach-Object { $msixPath = $_.FullName &amp; AzureSignTool sign -kvu "$<SPAN class="hljs-template-variable">{{ secrets.AzureClientUrl }}</SPAN>" -kvi "$<SPAN class="hljs-template-variable">{{ secrets.AzureClientId }}</SPAN>" -kvs "$<SPAN class="hljs-template-variable">{{ secrets.AzureClientSecret }}</SPAN>" -kvc $<SPAN class="hljs-template-variable">{{ secrets.AzureClientName }}</SPAN> -tr http://timestamp.digicert.com -v $msixPath } </SPAN></CODE></PRE> <P>In this case, there are a few differences compared to the Azure DevOps one. The first obvious one is that GitHub uses a different syntax to access to the secrets, which is<SPAN>&nbsp;</SPAN><CODE>${{ secrets.SECRET_NAME }}</CODE>. As such, the various parameters are filled with the values we have previously created in the Secrets section. The other one is that I'm using a slightly different approach to find the MSIX packages to sign. I'm using a PowerShell script which iterates through all the files stored in the build output and, only if they have the .msix extension, then I go on and use the Azure SignTool utility on it.</P> <P>&nbsp;</P> <P>That's it. Now you can add additional tasks to the pipeline to take care of the deployment of the signed MSIX package on a website, on the Microsoft Store or any other supported approach.</P> <P>&nbsp;</P> <H3 id="signing-with-visual-studio">Signing with Visual Studio</H3> <P>As a last "bonus information", Visual Studio 16.6 has recently added support for Azure Key Vault when you generate MSIX packages. This could be a great alternative to Azure SignTool if you're generating MSIX packages locally using the<A href="#" target="_self"> Windows Application Packaging Project</A>. To enable this option you just have to right click on the Windows Application Packaging Project included in your solution and choose<SPAN>&nbsp;</SPAN><STRONG>Publish → Create app packages</STRONG>. After you have chosen the path to create a package for sideloading (apps published on the Microsoft Store don't need to be signed, since the Store submission process will take care of that), you will see a new option in the list:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VisualStudioAzureKeyVault.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196192i89B1327FD8827BEE/image-size/large?v=v2&amp;px=999" role="button" title="VisualStudioAzureKeyVault.png" alt="VisualStudioAzureKeyVault.png" /></span></P> <P>&nbsp;</P> <P><SPAN>By pressing the&nbsp;</SPAN><STRONG>Select From Azure Key Vault</STRONG><SPAN>&nbsp;button you will have the opportunity to login with your Azure account, specify the URL of your Azure Key Vault and select one of the available certificates, as you can see from the image below:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VisualStudioPickCertificate.png" style="width: 775px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/196193iC70B7180F081F717/image-size/large?v=v2&amp;px=999" role="button" title="VisualStudioPickCertificate.png" alt="VisualStudioPickCertificate.png" /></span></SPAN></P> <P>&nbsp;</P> <P>However, I would recommend using this feature only for some specific scenarios, like you need to quickly generate and share a MSIX package with a colleague. The best approach to generate MSIX packages is to leverage a CI/CD pipeline to automate the process and to use Azure SignTool as part of it.</P> <H3 id="wrapping-up">Wrapping up</H3> <P>Having a solid signing story when you choose to distribute your Windows desktop application with MSIX is critical. In this blog post we have seen how we can use Azure Key Vault to protect our code-signing certificate (and even generate it) and then, using an utility called Azure SignTool, sign the MSIX package. This approach can be extremely helpful when you enable a CI/CD approach to automatically build and deploy your MSIX packages, since you need to find the right balance between convenience (you need to find an easy to way to sign the packages as part of your pipeline) and security (you can't store in public your private certificate). Azure Key Vault and Azure SignTool are a great combination to solve this problem, since they give you the opportunity to sign your MSIX packages as part of the CI/CD pipeline but without storing it in the repository or in the Azure DevOps / GitHub project.</P> <P>&nbsp;</P> <P>You can take a look an existing workflow on GitHub which uses this approach<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">here</A>.</P> <P>&nbsp;</P> <P>Before wrapping up, I want to thank <A href="#" target="_self">Claire Novotny</A> who helped me figuring out a couple of issues around authentication with Azure Key Vault.</P> <P>&nbsp;</P> <P>Happy packaging!</P> Thu, 04 Jun 2020 06:52:06 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/signing-a-msix-package-with-azure-key-vault/ba-p/1436154 Matteo Pagani 2020-06-04T06:52:06Z WinUI 3.0 Preview1 を触ってみよう https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/winui-3-0-preview1-%E3%82%92%E8%A7%A6%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86/ba-p/1407784 <P>Build 2020 のタイミングで Windows UI Library 3.0 Preview 1 が出ました。</P> <P>まだまだ制限事項がたくさんありますが、Win32 アプリもサポートされている点がとっても興味深いリリースになります。</P> <P>&nbsp;</P> <P><A href="#" target="_self">Windows UI ライブラリ 3.0 Preview 1 (2020 年 5 月)</A></P> <P>&nbsp;</P> <P>上記のリリース ノートにもあるように現段階では実稼働のアプリでの利用は想定されていませんが、フィードバックの収集が目的の一つでもあるので、是非正式リリースされたら使ってみようと思っている方も是非試してみてフィードバックをお願いします。(<A href="#" target="_self">GitHub のリポジトリ</A>でフィードバック可能です)</P> <P>&nbsp;</P> <P>色々な制限事項がありますが、日本語での利用時に致命的な制限事項として IME が機能しないという制限事項があります。その点だけは注意して利用してください。完全な制限事項のリストは、リリースノートに記載してあります。</P> <P>&nbsp;</P> <H2>下準備</H2> <P>では、早速使ってみようと思います。<A href="#" target="_self">Get started with WinUI 3.0 for desktop apps</A> にセットアップ方法があります。Windows 10 のバージョン以外には Visual Studio 2019 16.7 Preview 1 と .NET 5 Preview 4 と、プロジェクトテンプレート用の Visual Studio の拡張機能が必要になります。</P> <P>&nbsp;</P> <P>下準備が完了すると、以下のように WinUI 系のプロジェクトテンプレートが追加されます。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_0-1590051505677.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/193509iFC56644F92D4B1FD/image-size/large?v=v2&amp;px=999" role="button" title="KazukiOta_0-1590051505677.png" alt="KazukiOta_0-1590051505677.png" /></span></P> <P>Blank App, Packaged (WinUI in Desktop) を試してみようと思います。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="winui1.gif" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/193513iC4492C6CFEA9E377/image-size/medium?v=v2&amp;px=400" role="button" title="winui1.gif" alt="winui1.gif" /></span></P> <P>&nbsp;</P> <H2>プロジェクトの作成と中身の確認</H2> <P>このプロジェクトを新規作成すると一見 WPF アプリのプロジェクトに見えるプロジェクトと、MSIX にパッケージングするための Windows アプリケーション パッケージ プロジェクトが作成されます。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_0-1590052218556.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/193512i071B945049011175/image-size/medium?v=v2&amp;px=400" role="button" title="KazukiOta_0-1590052218556.png" alt="KazukiOta_0-1590052218556.png" /></span></P> <P>App クラスや MainWindow クラスのベースクラスも Microsoft.UI.Xaml.Application クラスや Microsoft.UI.Xaml.Window クラスなので WPF とは全く別物であることがわかります。</P> <P>実行してみると、ひな型では以下のようにボタンがあるだけの非常にシンプルな画面が起動してきます。私の Windows 10 は、今ダークモードにしているのでそれが反映されてウィンドウの背景とボタンが黒っぽくなっています。</P> <P>ここら辺が、何も考えなくても反映されるのはとてもありがたいと思います。</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="winui1.gif" style="width: 781px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/193514i1B7949700A081B28/image-size/large?v=v2&amp;px=999" role="button" title="winui1.gif" alt="winui1.gif" /></span></P> <P>プロジェクトファイルを確認すると、.NET 5 であることと WinExe を出力すること、あとは Microsoft.WinUI パッケージを参照していることが確認できました。</P> <P>&nbsp;</P> <LI-CODE lang="html">&lt;Project Sdk="Microsoft.NET.Sdk"&gt; &lt;PropertyGroup&gt; &lt;OutputType&gt;WinExe&lt;/OutputType&gt; &lt;TargetFramework&gt;netcoreapp5.0&lt;/TargetFramework&gt; &lt;TargetPlatformVersion&gt;10.0.18362.0&lt;/TargetPlatformVersion&gt; &lt;TargetPlatformMinVersion&gt;10.0.17134.0&lt;/TargetPlatformMinVersion&gt; &lt;RootNamespace&gt;HelloWorldApp&lt;/RootNamespace&gt; &lt;ApplicationManifest&gt;app.manifest&lt;/ApplicationManifest&gt; &lt;Platforms&gt;AnyCPU;x86;x64&lt;/Platforms&gt; &lt;SelfContained&gt;true&lt;/SelfContained&gt; &lt;RuntimeIdentifiers&gt;win-x86;win-x64&lt;/RuntimeIdentifiers&gt; &lt;RuntimeIdentifier&gt;win-$(Platform)&lt;/RuntimeIdentifier&gt; &lt;/PropertyGroup&gt; &lt;ItemGroup&gt; &lt;PackageReference Include="Microsoft.VCRTForwarders.140" Version="1.0.6" /&gt; &lt;PackageReference Include="Microsoft.WinUI" Version="3.0.0-preview1.200515.3" /&gt; &lt;Manifest Include="$(ApplicationManifest)" /&gt; &lt;/ItemGroup&gt; &lt;ItemGroup&gt; &lt;None Remove="App.xaml" /&gt; &lt;None Remove="MainWindow.xaml" /&gt; &lt;/ItemGroup&gt; &lt;ItemGroup&gt; &lt;Page Update="App.xaml"&gt; &lt;Generator&gt;MSBuild:Compile&lt;/Generator&gt; &lt;/Page&gt; &lt;Page Update="MainWindow.xaml"&gt; &lt;Generator&gt;MSBuild:Compile&lt;/Generator&gt; &lt;/Page&gt; &lt;/ItemGroup&gt; &lt;/Project&gt;</LI-CODE> <P>&nbsp;</P> <P>コードの方を少しいじってみようと思います。手前味噌ではありますが、ReactiveProperty を追加して、ViewModel クラスを使って ReactiveProperty を使った入力に対して少し遅れて大文字に変換して画面に表示するものを試してみたいと思います。</P> <P>&nbsp;</P> <P>NuGet から ReactiveProperty v7.0.1 を追加して、MainWindowViewModel クラスを追加して以下のように書いてみました。</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using Reactive.Bindings; using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reactive.Linq; namespace HelloWorldApp { public class MainWindowViewModel { [Required(ErrorMessage = "{0} is required.")] public ReactiveProperty&lt;string&gt; Input { get; } public ReadOnlyReactiveProperty&lt;string&gt; InputErrorMessage { get; } public ReadOnlyReactiveProperty&lt;string&gt; Output { get; } public ReactiveCommand ResetCommand { get; } public MainWindowViewModel() { Input = new ReactiveProperty&lt;string&gt;("", ReactivePropertyMode.Default | ReactivePropertyMode.IgnoreInitialValidationError) .SetValidateAttribute(() =&gt; Input); InputErrorMessage = Input.ObserveValidationErrorMessage() .ToReadOnlyReactiveProperty(); Output = Input.Delay(TimeSpan.FromSeconds(2)) .Select(x =&gt; x.ToUpper()) .ToReadOnlyReactiveProperty(); ResetCommand = Input.Select(x =&gt; !string.IsNullOrEmpty(x)) .ToReactiveCommand() .WithSubscribe(() =&gt; Input.Value = ""); } } } </LI-CODE> <P>&nbsp;</P> <P>そして、MainWindow に、これをバインドします。バインドは、Preview 1 の段階ではコンパイル時データバインディングのみになっているようです。今後もそうなのかは不明ですが、DataContext プロパティが Window になくてびっくりしました。</P> <P>というか新規に作るならコンパイル時データバインディングだけでも困ることは、ほとんどないのでいいですよね。</P> <P>&nbsp;</P> <P>ということで MainWindow.xaml を以下のようにします。あわせて MainWindow.xaml.cs から、デフォルトのボタンクリックイベントハンドラも消して ViewModel のプロパティを追加します。</P> <P>&nbsp;</P> <LI-CODE lang="html">&lt;Window x:Class="HelloWorldApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:HelloWorldApp" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"&gt; &lt;StackPanel&gt; &lt;TextBox Text="{x:Bind ViewModel.Input.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /&gt; &lt;TextBlock Text="{x:Bind ViewModel.InputErrorMessage.Value, Mode=OneWay}" Foreground="Red" /&gt; &lt;Button Content="Reset" Command="{x:Bind ViewModel.ResetCommand}" /&gt; &lt;TextBlock Text="{x:Bind ViewModel.Output.Value, Mode=OneWay}" /&gt; &lt;/StackPanel&gt; &lt;/Window&gt; </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using Microsoft.UI.Xaml; // The Blank Window item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&amp;clcid=0x409 namespace HelloWorldApp { /// &lt;summary&gt; /// An empty window that can be used on its own or navigated to within a Frame. /// &lt;/summary&gt; public sealed partial class MainWindow : Window { private MainWindowViewModel ViewModel { get; } = new MainWindowViewModel(); public MainWindow() { this.InitializeComponent(); } } } </LI-CODE> <P>&nbsp;</P> <P>&nbsp;出来たので実行してみましょう。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="winui2.gif" style="width: 687px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/193520i71DC05E371B82FBA/image-size/large?v=v2&amp;px=999" role="button" title="winui2.gif" alt="winui2.gif" /></span></P> <P>ちゃんと 2 秒遅れて大文字になって表示されてますね。</P> <P>&nbsp;</P> <H2>まとめ</H2> <P>ということで、Windows UI Library 3.0 Preview 1 が出たので、その中の Win 32 アプリのプロジェクトテンプレートを試してみました。</P> <P>かなりシームレスに Win UI Library がアプリ内で使えることがわかりました。今回の Preview 1 ではサポートされていませんが、XAML Islands の機能が追加されると WPF などの従来のアプリに組み込むことも出来ると思います。ViewModel をきちんと作ってるアプリであれば View だけ移植するのも、そこまで大変じゃないかもしれないという淡い期待が持てそうだと思います。</P> <P>&nbsp;</P> <P>ReactiveProperty も普通に動いてバリデーションもちゃんと動いていました。</P> <P>&nbsp;</P> <P>是非、皆さんも面白そうだなと思ったらインストールして試してみてください。</P> Thu, 21 May 2020 09:55:28 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/winui-3-0-preview1-%E3%82%92%E8%A7%A6%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86/ba-p/1407784 KazukiOta 2020-05-21T09:55:28Z Turning to the dark side of inking = UnprocessedInput https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/turning-to-the-dark-side-of-inking-unprocessedinput/ba-p/1364331 <DIV id="MainContent"> <P>We learned how to draw on an InkCanvas and 'convert' the lines drawn into nice polished <A href="#" target="_blank" rel="noopener">XAML lines</A> with the previous article <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">Use the UWP Inking platform as input for advanced scenarios</A>. Let's go further and implement a "cut the lines" functionality in order to be able to divide a line in two lines like we would do using a scissor ✂.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="initial-code-for-handling-inking-and-creating-xaml-lines"><A id="pragma-line-4" target="_blank"></A>Initial code for handling inking and creating XAML Lines</H2> <P>For the starting point, we take the <A title="StrokesToShapes sample code" href="#" target="_blank" rel="noopener">sample code</A> related to the article we just mentioned before.</P> <P>As a reminder, we have two canvas:</P> <UL id="pragma-line-8"> <LI id="pragma-line-8">One InkCanvas for accepting the drawings.</LI> <LI id="pragma-line-9">One Canvas for the resulting XAML Shapes.</LI> </UL> <PRE><CODE id="pragma-line-11" class="language-csharp hljs">&lt;InkCanvas x:Name=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt; &lt;Canvas x:Name=<SPAN class="hljs-string">"ShapesCanvas"</SPAN> /&gt; </CODE></PRE> <P>The code behind for 'converting' strokes is displayed below. When the user finishes to draw a line (<EM>StrokesCollected</EM> event), we create a XAML Line with the same origin and end as the stroke. Then, we add this XAML line to the canvas and remove the stroke:</P> <PRE><CODE id="pragma-line-17" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-title">MainPage</SPAN>()</SPAN> { <SPAN class="hljs-keyword">this</SPAN>.InitializeComponent(); <SPAN class="hljs-comment">// Initialize the InkCanvas</SPAN> inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch; <SPAN class="hljs-comment">// When the user finished to draw something on the InkCanvas</SPAN> inkCanvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected; } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">InkPresenter_StrokesCollected</SPAN>(<SPAN class="hljs-params"> Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args</SPAN>)</SPAN> { InkStroke stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Last(); Line line = ConvertStrokeToXAMLLine(stroke); ShapesCanvas.Children.Add(line); <SPAN class="hljs-comment">// We delete the InkStroke from the InkCanvas</SPAN> stroke.Selected = <SPAN class="hljs-literal">true</SPAN>; inkCanvas.InkPresenter.StrokeContainer.DeleteSelected(); } <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> Line <SPAN class="hljs-title">ConvertStrokeToXAMLLine</SPAN>(<SPAN class="hljs-params">InkStroke stroke</SPAN>)</SPAN> { <SPAN class="hljs-keyword">var</SPAN> line = <SPAN class="hljs-keyword">new</SPAN> Line(); line.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Green); line.StrokeThickness = <SPAN class="hljs-number">6</SPAN>; <SPAN class="hljs-comment">// The origin = (X1, Y1)</SPAN> line.X1 = stroke.GetInkPoints().First().Position.X; line.Y1 = stroke.GetInkPoints().First().Position.Y; <SPAN class="hljs-comment">// The end = (X2, Y2)</SPAN> line.X2 = stroke.GetInkPoints().Last().Position.X; line.Y2 = stroke.GetInkPoints().Last().Position.Y; <SPAN class="hljs-keyword">return</SPAN> line; } </CODE></PRE> <P>Here is the initial application:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="InitialProject.gif" style="width: 534px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/189512i6F16343B7087922B/image-size/large?v=v2&amp;px=999" role="button" title="InitialProject.gif" alt="Initial windows displaying strokes converted in XAML lines" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Initial windows displaying strokes converted in XAML lines</span></span></P> <P>&nbsp;</P> <P>We can start now!</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="the-idea-use-the-inking-input-to-be-able-to-cut-lines"><A id="pragma-line-68" target="_blank"></A>The idea: Use the inking input to be able to 'cut' lines</H2> <P>We have the InkCanvas there, ready to accept pen/mouse/touch inputs. It would be great if we could leverage on it to accept interaction for drawing our 'cutting line'. The InkCanvas allows us to do it with <STRONG><A href="#" target="_blank" rel="noopener">UnprocessedInput</A></STRONG>.</P> <P>&nbsp;</P> <BLOCKQUOTE id="pragma-line-70"> <P>The idea is to allow the InkCanvas to take input (we would like the user to draw a line to cut the XAML lines) but we do not flow this input to the InkPresenter to display the default digital ink. Instead, this input is handled only by our code.</P> </BLOCKQUOTE> <P>Let's first modify the XAML in order to add a toggle switch to change the input mode between "allow inking" and "cut lines". To do so, we add a grid with two lines. The first line takes the <A href="#" target="_blank" rel="noopener">ToogleSwitch</A> control and adapts its height based on the height of the button. The second line takes all the remaining space of the window and shows the two canvas (Let's put the LightGray background color for this line):</P> <PRE><CODE id="pragma-line-75" class="language-csharp hljs">&lt;Grid&gt; &lt;Grid.RowDefinitions&gt; &lt;RowDefinition Height=<SPAN class="hljs-string">"Auto"</SPAN>&gt;&lt;/RowDefinition&gt; &lt;RowDefinition&gt;&lt;/RowDefinition&gt; &lt;/Grid.RowDefinitions&gt; &lt;Grid Grid.Row=<SPAN class="hljs-string">"0"</SPAN>&gt; &lt;ToggleSwitch x:Name=<SPAN class="hljs-string">"cutAShapeToggleSwitch"</SPAN> OffContent=<SPAN class="hljs-string">"Cut lines Off"</SPAN> OnContent=<SPAN class="hljs-string">"Cut lines On"</SPAN> Toggled=<SPAN class="hljs-string">"ToggleSwitch_Toggled"</SPAN>/&gt; &lt;/Grid&gt; &lt;Grid Grid.Row=<SPAN class="hljs-string">"1"</SPAN> Background=<SPAN class="hljs-string">"LightGray"</SPAN>&gt; &lt;Canvas x:Name=<SPAN class="hljs-string">"ShapesCanvas"</SPAN> /&gt; &lt;InkCanvas x:Name=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt; &lt;/Grid&gt; &lt;/Grid&gt; </CODE></PRE> <P>The ToggleSwitch is 'off' by default with the <EM>OffContent</EM> text displayed. If we toggle the control, it will be 'on' and will display the <EM>OnContent</EM> text. And so on... We get the toggle's changes in the code behind with the <EM>ToggleSwitch_Toggled</EM> event handler:</P> <PRE><CODE id="pragma-line-100" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">ToggleSwitch_Toggled</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, RoutedEventArgs e</SPAN>)</SPAN> { ToggleSwitch toggleSwitch = sender <SPAN class="hljs-keyword">as</SPAN> ToggleSwitch; <SPAN class="hljs-keyword">if</SPAN> (toggleSwitch.IsOn == <SPAN class="hljs-literal">true</SPAN>) { <SPAN class="hljs-comment">// We go to the "cut the lines" mode</SPAN> } <SPAN class="hljs-keyword">else</SPAN> { <SPAN class="hljs-comment">// We go back to normal</SPAN> } } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="unprocessedinput-unleashed"><A id="pragma-line-116" target="_blank"></A>UnprocessedInput unleashed</H2> <P>How can we switch to the "cut the lines" mode and 'take the control' of the inputs? Simply by modifying the inking processing behavior and not accept inputs for inking or erasing. This is done by modifying the <EM><STRONG><A href="#" target="_blank" rel="noopener">InputProcessingConfiguration</A></STRONG></EM> property of the InkPresenter:</P> <PRE><CODE id="pragma-line-118" class="language-csharp hljs">inkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None; </CODE></PRE> <P>Now, as the inking is not processed anymore, we have to do it with our code. We have three events to handle for this:</P> <UL id="pragma-line-124"> <LI id="pragma-line-124"><STRONG><A href="#" target="_blank" rel="noopener">PointerPressed</A></STRONG> - Every drawing starts with this event. It allows us to execute an initialization code when the user start to draw. Typically, we initiate a XAML Shapes that we will modify for every movement of the pen/touch/mouse.</LI> <LI id="pragma-line-125"><STRONG><A href="#" target="_blank" rel="noopener">PointerMoved</A></STRONG> - This event is raised when the user is drawing on the InkCanvas. We can then handle the input and draw or modify some XAML Shapes to follow the input's changes for example.</LI> <LI id="pragma-line-126"><STRONG><A href="#" target="_blank" rel="noopener">PointerReleased</A></STRONG> - When the user finished the drawing i.e. when the device surface is not touched anymore, we consider the drawing finished. We can decide to perform actions like selecting/modifying shapes or launch a shapes/character recognition.</LI> </UL> <PRE><CODE id="pragma-line-127" class="language-csharp hljs">inkCanvas.InkPresenter.UnprocessedInput.PointerPressed += UnprocessedInput_PointerPressed; inkCanvas.InkPresenter.UnprocessedInput.PointerMoved += UnprocessedInput_PointerMoved; inkCanvas.InkPresenter.UnprocessedInput.PointerReleased += UnprocessedInput_PointerReleased; </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="the-cut-the-lines-visual-feedback"><A id="pragma-line-136" target="_blank"></A>The "cut the lines" visual feedback</H2> <P>Let's start: When the user toggle 'on', we want to be in teh "cut the lines" mode:</P> <UL id="pragma-line-138"> <LI id="pragma-line-138">We put the <EM>InputProcessingConfiguration.Mode</EM> to <EM>InkInputProcessingMode.None</EM>.</LI> <LI id="pragma-line-139">We go to <EM>UnprocessedInput</EM> by handling the three events.</LI> </UL> <PRE><CODE id="pragma-line-141" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">ToggleSwitch_Toggled</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, RoutedEventArgs e</SPAN>)</SPAN> { ToggleSwitch toggleSwitch = sender <SPAN class="hljs-keyword">as</SPAN> ToggleSwitch; <SPAN class="hljs-keyword">var</SPAN> p = inkCanvas.InkPresenter; <SPAN class="hljs-keyword">if</SPAN> (toggleSwitch.IsOn == <SPAN class="hljs-literal">true</SPAN>) { <SPAN class="hljs-comment">// We are not in the inking or erasing mode </SPAN> <SPAN class="hljs-comment">// ==&gt; Inputs are redirected to UnprocessedInput</SPAN> <SPAN class="hljs-comment">// i.e. Our code will take care of the inking inputs</SPAN> p.InputProcessingConfiguration.Mode = InkInputProcessingMode.None; p.UnprocessedInput.PointerPressed += UnprocessedInput_PointerPressed; p.UnprocessedInput.PointerMoved += UnprocessedInput_PointerMoved; p.UnprocessedInput.PointerReleased += UnprocessedInput_PointerReleased; } <SPAN class="hljs-keyword">else</SPAN> { <SPAN class="hljs-comment">// Go back to normal which is the inking mode</SPAN> <SPAN class="hljs-comment">// ==&gt; We can draw lines on the InkCanvas</SPAN> inkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking; <SPAN class="hljs-comment">// Remove the handlers for UnprocessedInput</SPAN> p.UnprocessedInput.PointerPressed -= UnprocessedInput_PointerPressed; p.UnprocessedInput.PointerMoved -= UnprocessedInput_PointerMoved; p.UnprocessedInput.PointerReleased -= UnprocessedInput_PointerReleased; } } </CODE></PRE> <P>When the user starts to draw the 'cutting line', we are in the <EM>PointerPressed</EM> event handler. Here are the actions we do:</P> <UL id="pragma-line-174"> <LI id="pragma-line-174">We create a <A href="#" target="_blank" rel="noopener">XAML Polyline</A> which allows us to 'draw' connected straight lines to display a dotted scissor line: <CODE>lasso = new Polyline()</CODE>.</LI> <LI id="pragma-line-175">The Polyline will be a red dotted line like "- - -". This is what means the <CODE>DoubleCollection() { 2, 2 }</CODE> for the <EM>StrokeDashArray</EM> property.</LI> <LI id="pragma-line-176">The initial position of the finger/mouse/pen is used for the polyline: <CODE>lasso.Points.Add(args.CurrentPoint.RawPosition);</CODE>.</LI> <LI id="pragma-line-177">Lastly, the polyline is added to the canvas by using <CODE>ShapesCanvas.Children.Add(lasso);</CODE>.</LI> </UL> <PRE><CODE id="pragma-line-179" class="language-csharp hljs"><SPAN class="hljs-comment">// The lasso is used to cut the lines when we switch to UnprocessedInput</SPAN> <SPAN class="hljs-keyword">private</SPAN> Polyline lasso; </CODE></PRE> <PRE><CODE id="pragma-line-184" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">UnprocessedInput_PointerPressed</SPAN>(<SPAN class="hljs-params"> InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args</SPAN>)</SPAN> { lasso = <SPAN class="hljs-keyword">new</SPAN> Polyline() { Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Red), StrokeThickness = <SPAN class="hljs-number">2</SPAN>, StrokeDashArray = <SPAN class="hljs-keyword">new</SPAN> DoubleCollection() { <SPAN class="hljs-number">2</SPAN>, <SPAN class="hljs-number">2</SPAN> } }; lasso.Points.Add(args.CurrentPoint.RawPosition); ShapesCanvas.Children.Add(lasso); } </CODE></PRE> <P>For intermediate changes, when the user is drawing, it is pretty straightforward: We just add an additional point to the polyline:</P> <PRE><CODE id="pragma-line-203" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">UnprocessedInput_PointerMoved</SPAN>(<SPAN class="hljs-params"> InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args</SPAN>)</SPAN> { lasso.Points.Add(args.CurrentPoint.RawPosition); } </CODE></PRE> <DIV id="tinyMceEditorSebastien Bovo_1" class="mceNonEditable lia-copypaste-placeholder">&nbsp;</DIV> <P>&nbsp;</P> <H2 id="the-cut-the-lines-ending-code"><A id="pragma-line-216" target="_blank"></A>The "cut the lines" ending code</H2> <P>When the 'cutting line' drawn is finished, we have some work:</P> <UL id="pragma-line-218"> <LI id="pragma-line-218">For each single XAML line of the canvas, we have to detect if it was cut or not. If the line was cut, we have to replace it by the two corresponding lines. This implies finding the intersection point and create the first resulting line from the origin to the intersection point and the second line for the intersection point to the end. Finally, we remove the original line which was cut.</LI> <LI id="pragma-line-219">Remove the 'cutting line' (the <CODE>lasso</CODE> variable) from the canvas because we do not need it anymore.</LI> </UL> <PRE><CODE id="pragma-line-222" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">UnprocessedInput_PointerReleased</SPAN>(<SPAN class="hljs-params"> InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args</SPAN>)</SPAN> { lasso.Points.Add(args.CurrentPoint.RawPosition); <SPAN class="hljs-comment">// For each line of the Canvas, we look for an intersection</SPAN> <SPAN class="hljs-comment">// If any, we replace the line which has been cut by the two lines</SPAN> ShapesCanvas.Children.Remove(lasso); } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="finding-the-intersections"><A id="pragma-line-240" target="_blank"></A>Finding the intersections</H2> <P>To be honest, this part of the job was the most difficult. I first tried to make calculations with a sheet of paper and a pencil. I do not know if I would have been able to build an algorithm but I switched quickly to my second option which was do a search on Internet. And guess what! I found that a person named <A href="#" target="_blank" rel="noopener">Rod Stephens</A> were providing the exact code I need! Unbelievable! I contacted Rod and he said Yes: "Feel free to use the code and explanation". Thank you very much Rod! :call_me_hand:</img></P> <P>You can find the original blog post of Rod at this url: Determine where two lines intersect in C# - <A href="#" target="_blank" rel="noopener">http://csharphelper.com/blog/2014/08/determine-where-two-lines-intersect-in-c/</A>.</P> <P>Here is the function I used in my project.</P> <UL id="pragma-line-246"> <LI id="pragma-line-246">p1, p2, p3, p4 are points with (X,Y) coordinates.</LI> <LI id="pragma-line-247">This function is used for each line of the canvas. The line goes from p1 to p2.</LI> <LI id="pragma-line-248">The 'cutting line' goes from p3 to p4.</LI> <LI id="pragma-line-249"><CODE>segments_intersect</CODE> is the boolean we get indicating if the line was cut or not.</LI> <LI id="pragma-line-250"><CODE>intersection</CODE> is the intersection point we will use to create the two new lines based on the line which was cut.</LI> </UL> <BLOCKQUOTE id="pragma-line-252"> <P><STRONG>Note</STRONG>: There are extra parameters that we do not use in our sample application. You may need them based on your needs. So we chose to not change the signature of the function.</P> </BLOCKQUOTE> <PRE><CODE id="pragma-line-254" class="language-csharp hljs"><SPAN class="hljs-comment">// Code provided by Rod Stephens</SPAN> <SPAN class="hljs-comment">// at http://csharphelper.com/blog/2014/08/determine-where-two-lines-intersect-in-c/</SPAN> <SPAN class="hljs-comment">// Find the point of intersection between</SPAN> <SPAN class="hljs-comment">// the lines p1 --&gt; p2 and p3 --&gt; p4.</SPAN> <SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">FindIntersection</SPAN>(<SPAN class="hljs-params"> PointF p1, PointF p2, PointF p3, PointF p4, <SPAN class="hljs-keyword">out</SPAN> <SPAN class="hljs-keyword">bool</SPAN> lines_intersect, <SPAN class="hljs-keyword">out</SPAN> <SPAN class="hljs-keyword">bool</SPAN> segments_intersect, <SPAN class="hljs-keyword">out</SPAN> PointF intersection, <SPAN class="hljs-keyword">out</SPAN> PointF close_p1, <SPAN class="hljs-keyword">out</SPAN> PointF close_p2</SPAN>)</SPAN> { <SPAN class="hljs-comment">// Get the segments' parameters.</SPAN> <SPAN class="hljs-keyword">float</SPAN> dx12 = p2.X - p1.X; <SPAN class="hljs-keyword">float</SPAN> dy12 = p2.Y - p1.Y; <SPAN class="hljs-keyword">float</SPAN> dx34 = p4.X - p3.X; <SPAN class="hljs-keyword">float</SPAN> dy34 = p4.Y - p3.Y; <SPAN class="hljs-comment">// Solve for t1 and t2</SPAN> <SPAN class="hljs-keyword">float</SPAN> denominator = (dy12 * dx34 - dx12 * dy34); <SPAN class="hljs-keyword">float</SPAN> t1 = ((p1.X - p3.X) * dy34 + (p3.Y - p1.Y) * dx34) / denominator; <SPAN class="hljs-keyword">if</SPAN> (<SPAN class="hljs-keyword">float</SPAN>.IsInfinity(t1)) { <SPAN class="hljs-comment">// The lines are parallel (or close enough to it).</SPAN> lines_intersect = <SPAN class="hljs-literal">false</SPAN>; segments_intersect = <SPAN class="hljs-literal">false</SPAN>; intersection = <SPAN class="hljs-keyword">new</SPAN> PointF(<SPAN class="hljs-keyword">float</SPAN>.NaN, <SPAN class="hljs-keyword">float</SPAN>.NaN); close_p1 = <SPAN class="hljs-keyword">new</SPAN> PointF(<SPAN class="hljs-keyword">float</SPAN>.NaN, <SPAN class="hljs-keyword">float</SPAN>.NaN); close_p2 = <SPAN class="hljs-keyword">new</SPAN> PointF(<SPAN class="hljs-keyword">float</SPAN>.NaN, <SPAN class="hljs-keyword">float</SPAN>.NaN); <SPAN class="hljs-keyword">return</SPAN>; } lines_intersect = <SPAN class="hljs-literal">true</SPAN>; <SPAN class="hljs-keyword">float</SPAN> t2 = ((p3.X - p1.X) * dy12 + (p1.Y - p3.Y) * dx12) / -denominator; <SPAN class="hljs-comment">// Find the point of intersection.</SPAN> intersection = <SPAN class="hljs-keyword">new</SPAN> PointF(p1.X + dx12 * t1, p1.Y + dy12 * t1); <SPAN class="hljs-comment">// The segments intersect if t1 and t2 are between 0 and 1.</SPAN> segments_intersect = ((t1 &gt;= <SPAN class="hljs-number">0</SPAN>) &amp;&amp; (t1 &lt;= <SPAN class="hljs-number">1</SPAN>) &amp;&amp; (t2 &gt;= <SPAN class="hljs-number">0</SPAN>) &amp;&amp; (t2 &lt;= <SPAN class="hljs-number">1</SPAN>)); <SPAN class="hljs-comment">// Find the closest points on the segments.</SPAN> <SPAN class="hljs-keyword">if</SPAN> (t1 &lt; <SPAN class="hljs-number">0</SPAN>) { t1 = <SPAN class="hljs-number">0</SPAN>; } <SPAN class="hljs-keyword">else</SPAN> <SPAN class="hljs-keyword">if</SPAN> (t1 &gt; <SPAN class="hljs-number">1</SPAN>) { t1 = <SPAN class="hljs-number">1</SPAN>; } <SPAN class="hljs-keyword">if</SPAN> (t2 &lt; <SPAN class="hljs-number">0</SPAN>) { t2 = <SPAN class="hljs-number">0</SPAN>; } <SPAN class="hljs-keyword">else</SPAN> <SPAN class="hljs-keyword">if</SPAN> (t2 &gt; <SPAN class="hljs-number">1</SPAN>) { t2 = <SPAN class="hljs-number">1</SPAN>; } close_p1 = <SPAN class="hljs-keyword">new</SPAN> PointF(p1.X + dx12 * t1, p1.Y + dy12 * t1); close_p2 = <SPAN class="hljs-keyword">new</SPAN> PointF(p3.X + dx34 * t2, p3.Y + dy34 * t2); } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="the-cut-the-lines-ending-code-revealed"><A id="pragma-line-326" target="_blank"></A>The "cut the lines" ending code revealed</H2> <P>Let's put all the pieces together for effectively cutting all the lines which were intercepted.</P> <P>&nbsp;</P> <BLOCKQUOTE id="pragma-line-328"> <P><STRONG>Note</STRONG>: Of course you can do extra work to cut only one line and stop the UnprocessedInput code to check when a line is encoutered and cut.</P> </BLOCKQUOTE> <P>In our application, we decided to cut all lines for which we have an intersection with the 'cutting line' (the lasso). Here are the steps:</P> <UL id="pragma-line-331"> <LI id="pragma-line-331">Take notes about all the new lines we create. We use the <CODE>linesToAdd</CODE> list that we populate.</LI> <LI id="pragma-line-332">The <CODE>foreach (Line line in ShapesCanvas.Children.OfType&lt;Line&gt;()) { }</CODE> loop allows to perform the intersection check on every single XAML Line Shapes in the canvas.</LI> <LI id="pragma-line-333">The <CODE>FindIntersection</CODE> function is called for each XAML line.</LI> <LI id="pragma-line-334">We verify if there is an intersection with the boolean <CODE>SegmentIntersection</CODE>.</LI> <LI id="pragma-line-335">If yes, we call the <CODE>CutTheLine</CODE> function that we will describe later.</LI> <LI id="pragma-line-336">Finally, all the new lines are added to the canvas and we remove the lasso.</LI> </UL> <PRE><CODE id="pragma-line-338" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">UnprocessedInput_PointerReleased</SPAN>(<SPAN class="hljs-params">InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args</SPAN>)</SPAN> { lasso.Points.Add(args.CurrentPoint.RawPosition); List&lt;Line&gt; linesToAdd = <SPAN class="hljs-keyword">new</SPAN> List&lt;Line&gt;(); <SPAN class="hljs-keyword">foreach</SPAN> (Line line <SPAN class="hljs-keyword">in</SPAN> ShapesCanvas.Children.OfType&lt;Line&gt;()) { <SPAN class="hljs-keyword">bool</SPAN> LineIntersection = <SPAN class="hljs-literal">false</SPAN>; <SPAN class="hljs-keyword">bool</SPAN> SegmentIntersection = <SPAN class="hljs-literal">false</SPAN>; PointF IntersectionPoint; PointF p2; PointF p3; FindIntersection( <SPAN class="hljs-comment">// The line in the canvas</SPAN> <SPAN class="hljs-keyword">new</SPAN> PointF((<SPAN class="hljs-keyword">float</SPAN>)line.X1, (<SPAN class="hljs-keyword">float</SPAN>)line.Y1), <SPAN class="hljs-keyword">new</SPAN> PointF((<SPAN class="hljs-keyword">float</SPAN>)line.X2, (<SPAN class="hljs-keyword">float</SPAN>)line.Y2), <SPAN class="hljs-comment">// The 'cutting line'</SPAN> <SPAN class="hljs-keyword">new</SPAN> PointF((<SPAN class="hljs-keyword">float</SPAN>)lasso.Points.First().X, (<SPAN class="hljs-keyword">float</SPAN>)lasso.Points.First().Y), <SPAN class="hljs-keyword">new</SPAN> PointF((<SPAN class="hljs-keyword">float</SPAN>)lasso.Points.Last().X, (<SPAN class="hljs-keyword">float</SPAN>)lasso.Points.Last().Y), <SPAN class="hljs-keyword">out</SPAN> LineIntersection, <SPAN class="hljs-comment">// Indicate if there is an intersection</SPAN> <SPAN class="hljs-keyword">out</SPAN> SegmentIntersection, <SPAN class="hljs-comment">// The intersection point</SPAN> <SPAN class="hljs-keyword">out</SPAN> IntersectionPoint, <SPAN class="hljs-keyword">out</SPAN> p2, <SPAN class="hljs-keyword">out</SPAN> p3); <SPAN class="hljs-keyword">if</SPAN> (SegmentIntersection) { List&lt;Line&gt; lines = CutTheLine(line, IntersectionPoint); linesToAdd.AddRange(lines); } } <SPAN class="hljs-keyword">foreach</SPAN> (Line line <SPAN class="hljs-keyword">in</SPAN> linesToAdd) { ShapesCanvas.Children.Add(line); } ShapesCanvas.Children.Remove(lasso); } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="cutting-the-line"><A id="pragma-line-389" target="_blank"></A>Cutting the line</H2> <P>We have done the most complex work so far. Cutting the line is the easiest action to do when we have the original line and the intersection point:</P> <UL id="pragma-line-391"> <LI id="pragma-line-391">The first new line goes from the origin of the original line to the intersection</LI> <LI id="pragma-line-392">The second new line goes from the intersection to the end of the original line</LI> </UL> <P>We just change the color for fun and to be able to distinguish the two resulting lines.</P> <P>&nbsp;</P> <BLOCKQUOTE id="pragma-line-396"> <P>In an upcoming article, we will draw some 'anchors' to the line. It is another way to identify two distinct lines</P> </BLOCKQUOTE> <PRE><CODE id="pragma-line-399" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> List&lt;Line&gt; <SPAN class="hljs-title">CutTheLine</SPAN>(<SPAN class="hljs-params">Line lineToCut, PointF intersection</SPAN>)</SPAN> { List&lt;Line&gt; lines = <SPAN class="hljs-keyword">new</SPAN> List&lt;Line&gt;(); <SPAN class="hljs-keyword">var</SPAN> line1 = <SPAN class="hljs-keyword">new</SPAN> Line(); line1.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.DarkOrange); line1.StrokeThickness = <SPAN class="hljs-number">3</SPAN>; line1.X1 = lineToCut.X1; line1.Y1 = lineToCut.Y1; line1.X2 = intersection.X; line1.Y2 = intersection.Y; <SPAN class="hljs-keyword">var</SPAN> line2 = <SPAN class="hljs-keyword">new</SPAN> Line(); line2.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.DarkViolet); line2.StrokeThickness = <SPAN class="hljs-number">3</SPAN>; line2.X1 = intersection.X; line2.Y1 = intersection.Y; line2.X2 = lineToCut.X2; line2.Y2 = lineToCut.Y2; lines.Add(line1); lines.Add(line2); <SPAN class="hljs-keyword">return</SPAN> lines; } </CODE></PRE> <P>F I N A L L Y, we made it! Yes! Yes! We M A D E it! =)</img></P> <P>All the source code is available on GitHub - <A href="#" target="_blank" rel="noopener">https://github.com/microsoft/Windows-AppConsult-Samples-UWP/</A></P> <P>You want to see it in action? Look:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="CutLine02.gif" style="width: 544px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/189519iC7B3DF06CBCA8138/image-size/large?v=v2&amp;px=999" role="button" title="CutLine02.gif" alt="Window with the 'cut the lines' functionality" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Window with the 'cut the lines' functionality</span></span></P> <P>&nbsp;</P> <DIV id="tinyMceEditorSebastien Bovo_2" class="mceNonEditable lia-copypaste-placeholder">&nbsp;</DIV> <H2 id="wrapping-up"><A id="pragma-line-439" target="_blank"></A>Wrapping up</H2> <P><STRONG>UnprocessedInput</STRONG> is the key to be able to use the Inking inputs for performing advanced actions on strokes or XAML Shapes. We used it for 'cutting' lines but think about that you would be able to select shapes by intersecting them. We could imagine also putting points on a surface and when we press a extra button, we joint all the points to create a complex shape, etc..</P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="noopener">@sbovo</A> for the AppConsult team.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="inking-series-articles"><A id="pragma-line-446" target="_blank"></A>Inking series' articles</H2> <P>This article is part of a series exploring concepts about inking and XAML Shapes. Here are all links:</P> <OL id="pragma-line-448"> <LI id="pragma-line-448"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">Use the UWP Inking platform as input for advanced scenarios</A></P> </LI> <LI id="pragma-line-449"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/handling-zoom-in-inking-applications/ba-p/1354930" target="_blank" rel="noopener">Handling zoom in Inking applications</A></P> </LI> <LI id="pragma-line-451"> <P><STRONG>Turning to the dark side of inking = UnprocessedInput</STRONG> ⇐ You are here</P> </LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899" target="_blank" rel="noopener">Free your mind: Start manipulating XAML Shapes</A></LI> <LI> <DIV id="MainContent"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/xaml-shapes-manipulation-level-up/ba-p/1445405" target="_blank" rel="noopener">XAML Shapes manipulation level up</A></P> </DIV> </LI> </OL> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="references"><A id="pragma-line-453" target="_blank"></A>References</H2> <UL> <LI>Source code for this article -&nbsp;<A title="UnprocessedInput source code" href="#" target="_self">https://github.com/microsoft/Windows-AppConsult-Samples-UWP/</A>&nbsp;</LI> <LI id="pragma-line-454"><STRONG>ToogleSwitch</STRONG> control - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.toggleswitch</A></LI> <LI id="pragma-line-455">InkPresenter.<STRONG>UnprocessedInput</STRONG> Property - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.inking.inkpresenter.unprocessedinput</A></LI> <LI id="pragma-line-456">InputProcessingConfiguration - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.inking.inkpresenter.inputprocessingconfiguration</A></LI> <LI id="pragma-line-457">InkUnprocessedInput.<STRONG>PointerPressed</STRONG> Event - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.inking.inkunprocessedinput.pointerpressed</A></LI> <LI id="pragma-line-458">InkUnprocessedInput.<STRONG>PointerMoved</STRONG> Event - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.inking.inkunprocessedinput.pointermoved</A></LI> <LI id="pragma-line-459">InkUnprocessedInput.<STRONG>PointerReleased</STRONG> Event - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.input.inking.inkunprocessedinput.pointerreleased</A></LI> <LI id="pragma-line-460">XAML Polyline - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.shapes.polyline</A></LI> <LI id="pragma-line-461">Determine where two lines intersect in C# (by Rod Stephens) - <A href="#" target="_blank" rel="noopener">http://csharphelper.com/blog/2014/08/determine-where-two-lines-intersect-in-c/</A></LI> </UL> </DIV> Sat, 06 Jun 2020 16:06:44 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/turning-to-the-dark-side-of-inking-unprocessedinput/ba-p/1364331 Sebastien Bovo 2020-06-06T16:06:44Z Handling zoom in Inking applications https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/handling-zoom-in-inking-applications/ba-p/1354930 <DIV id="MainContent"> <P>In the previous article about <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">UWP Inking platform as input for advanced scenarios</A>, we have been able to</P> <UL id="pragma-line-3"> <LI id="pragma-line-3">Use the <A href="#" target="_blank" rel="noopener">InkCanvas</A> to accept pen/touch/mouse drawing</LI> <LI id="pragma-line-4">Convert the <A href="#" target="_blank" rel="noopener">InkStrokes</A> to <A href="#" target="_blank" rel="noopener">XAML Lines</A></LI> </UL> <P>We are about to see how we can easily integrate a zoom functionality in our inking application.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="the-scrollviewer-control-does-almost-all-the-magic"><A id="pragma-line-8" target="_blank"></A>The ScrollViewer control does 'almost' all the magic</H2> <P>You can start from the <A title="StrokesToShapes sample code" href="#" target="_blank" rel="noopener">code</A> explained in the previous <A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">blog post</A> or from a blank UWP application.</P> <P>We use the InkCanvas, but this time, we incorporate it inside a ScrollViewer:</P> <PRE><CODE id="pragma-line-12" class="language-csharp hljs">&lt;ScrollViewer x:Name=<SPAN class="hljs-string">"rootScrollViewer"</SPAN> ZoomMode=<SPAN class="hljs-string">"Enabled"</SPAN> MaxZoomFactor=<SPAN class="hljs-string">"5"</SPAN> MinZoomFactor=<SPAN class="hljs-string">"0.5"</SPAN> HorizontalScrollMode=<SPAN class="hljs-string">"Auto"</SPAN> HorizontalScrollBarVisibility=<SPAN class="hljs-string">"Auto"</SPAN> VerticalScrollMode=<SPAN class="hljs-string">"Auto"</SPAN> VerticalScrollBarVisibility=<SPAN class="hljs-string">"Auto"</SPAN>&gt; &lt;InkCanvas x:Name=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt; &lt;/ScrollViewer&gt; </CODE></PRE> <P>The important properties there are:</P> <UL id="pragma-line-23"> <LI id="pragma-line-23"><STRONG>ZoomMode</STRONG>="Enabled" in order to be able to zoom (That means also that we can block the zoom at some point of time in the app if needed)</LI> <LI id="pragma-line-24"><STRONG>MinZoomFactor</STRONG> and <STRONG>MaxZoomFactor</STRONG> to control the minimum and maximum zoom level we want</LI> <LI id="pragma-line-25"><STRONG>HorizontalScrollMode</STRONG> and <STRONG>VerticalScrollMode</STRONG> for giving the ability to move inside the content</LI> </UL> <P>You can use the mouse wheel to make vary the zoom level, but the touch is much more friendly and the best interaction to use for tablets or Surface devices. The C# code provided for the sample of the previous article was also using touch for drawing; For this scenario, we have to allow only the mouse and the pen to dedicate the touch for the zoom gesture:</P> <PRE><CODE id="pragma-line-29" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-title">MainPage</SPAN>()</SPAN> { <SPAN class="hljs-keyword">this</SPAN>.InitializeComponent(); <SPAN class="hljs-comment">// Initialize the InkCanvas</SPAN> inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen; } </CODE></PRE> <DIV id="tinyMceEditorSebastien Bovo_0" class="mceNonEditable lia-copypaste-placeholder">&nbsp;</DIV> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ZoomInteraction.gif" style="width: 534px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/188382i48CF547BF916882C/image-size/large?v=v2&amp;px=999" role="button" title="ZoomInteraction.gif" alt="Zoom interaction with strokes on an UWP window" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Zoom interaction with strokes on an UWP window</span></span></P> <P>&nbsp;</P> <H2 id="improving-the-zoom-experience"><A id="pragma-line-44" target="_blank"></A>Improving the zoom experience</H2> <P>Look at the following interactions about our sample. Do you notice the two limitations?</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="ZoomLimitationsShort.gif" style="width: 536px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/188385i9D50B3DD84D42E2F/image-size/large?v=v2&amp;px=999" role="button" title="ZoomLimitationsShort.gif" alt="Zoom limitations in a UWP Window displaying a InkCanvas" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Zoom limitations in a UWP Window displaying a InkCanvas</span></span></P> <OL id="pragma-line-49"> <LI id="pragma-line-49">The zoom is not focused on the area on which we do the zoom gesture (or not focused on the mouse cursor if we use the mouse wheel). The zoom always focus on the origin of the window</LI> <LI id="pragma-line-50">We cannot move inside the zoomed content in doing right/left or top/bottom gestures</LI> </OL> <P>This not really usable for professional drawing or content creation apps, no? Let's use an extremely small magic joker: No code, no work! Just using a <A href="#" target="_blank" rel="noopener">Viewbox</A> control :party_popper:</img> :</P> <PRE><CODE id="pragma-line-54" class="language-csharp hljs">&lt;Grid&gt; &lt;ScrollViewer x:Name=<SPAN class="hljs-string">"rootScrollViewer"</SPAN> ZoomMode=<SPAN class="hljs-string">"Enabled"</SPAN> MaxZoomFactor=<SPAN class="hljs-string">"5"</SPAN> MinZoomFactor=<SPAN class="hljs-string">"0.5"</SPAN> HorizontalScrollMode=<SPAN class="hljs-string">"Auto"</SPAN> HorizontalScrollBarVisibility=<SPAN class="hljs-string">"Auto"</SPAN> VerticalScrollMode=<SPAN class="hljs-string">"Auto"</SPAN> VerticalScrollBarVisibility=<SPAN class="hljs-string">"Auto"</SPAN>&gt; &lt;Viewbox x:Name=<SPAN class="hljs-string">"imageViewbox"</SPAN>&gt; &lt;Grid&gt; &lt;Image x:Name=<SPAN class="hljs-string">"floorplanImage"</SPAN> Source=<SPAN class="hljs-string">"assets/SplashScreen.scale-200.png"</SPAN>/&gt; &lt;InkCanvas x:Name=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt; &lt;/Grid&gt; &lt;/Viewbox&gt; &lt;/ScrollViewer&gt; &lt;/Grid&gt; </CODE></PRE> <P>With this code, you can zoom/unzoom focusing on a specific area and move freely inside the drawing:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="FullInteractionsShort.gif" style="width: 534px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/188425iEA80E06224C579E5/image-size/large?v=v2&amp;px=999" role="button" title="FullInteractionsShort.gif" alt="Zoom full interactions in a UWP Window displaying a InkCanvas" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Zoom full interactions in a UWP Window displaying a InkCanvas</span></span></P> <P>&nbsp;</P> <P>This sample XAML code is the shortest possible for being able to read it with simplicity. Feel free to use some RowDefinition/ColumnDefinition with fixed Height/Width to really control the surface on which you allow the user to draw. Here, the Viewbox provides all the functionalities we need to adapt the content to the available space in the Grid.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="considerations-for-pentouchmouse-inputs"><A id="pragma-line-80" target="_blank"></A>Considerations for pen/touch/mouse inputs</H2> <P>You have seen in this sample that we do not accept touch for drawing on the InkCanvas. We use the pen or the mouse for inking. You understand that you will have to make a choice for input types. For example, you can consider the following options.</P> <UL id="pragma-line-83"> <LI id="pragma-line-83">Allow only pen and mouse for drawing if you are using a device with a pen like Microsoft Surface; Then you dedicate the touch for zoom/move or other interactions.</LI> <LI id="pragma-line-84">Allow touch all the time for drawing if the device does not have a pen; You provide a button/switch in order to go to a "zoom/move" mode to be able to navigate inside the window's content.</LI> </UL> <P>Just remember that at any time, you can change the inputs allowed. Here is an example to enable or disable touch:</P> <PRE><CODE id="pragma-line-87" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">EnableTouchDrawing</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">bool</SPAN> enable</SPAN>)</SPAN> { <SPAN class="hljs-keyword">if</SPAN> (enable) { inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch | CoreInputDeviceTypes.Pen; } <SPAN class="hljs-keyword">else</SPAN> { inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen; } } </CODE></PRE> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="wrapping-up"><A id="pragma-line-106" target="_blank"></A>Wrapping up</H2> <P>Ink strokes or XAML Shapes that you are displaying on Canvas are vectorial shapes. Implementing functionalities to be able to move the 'sheet' on which you draw or zoom for a better visualization/precision is almost mandatory for inking applications. Define a strategy for handling and switching inputs is also key to provide the best user experience.</P> <P>--</P> <P>As always, the source code of the sample is on GitHub - <A href="#" target="_blank" rel="noopener">https://github.com/microsoft/Windows-AppConsult-Samples-UWP/</A></P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="noopener">@sbovo</A> for the AppConsult team.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="inking-series-articles">Inking series' articles</H2> <P>This article is part of a series exploring concepts about inking and XAML Shapes. Here are all links:</P> <OL id="pragma-line-448"> <LI id="pragma-line-448"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471" target="_blank" rel="noopener">Use the UWP Inking platform as input for advanced scenarios</A></P> </LI> <LI id="pragma-line-449"> <P data-unlink="true"><STRONG>Handling zoom in Inking applications</STRONG>&nbsp;⇐ You are here&nbsp;</P> </LI> <LI id="pragma-line-451"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/turning-to-the-dark-side-of-inking-unprocessedinput/ba-p/1364331" target="_blank" rel="noopener">Turning to the dark side of inking = UnprocessedInput&nbsp;</A></P> </LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899" target="_blank" rel="noopener">Free your mind: Start manipulating XAML Shapes</A></LI> <LI> <DIV id="MainContent"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/xaml-shapes-manipulation-level-up/ba-p/1445405" target="_blank" rel="noopener">XAML Shapes manipulation level up</A></P> </DIV> </LI> </OL> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="references"><A id="pragma-line-117" target="_blank"></A>References</H2> <UL id="pragma-line-118"> <LI>Source code of this article -&nbsp;<A title="InkingAndZoom source code" href="#" target="_self">https://github.com/microsoft/Windows-AppConsult-Samples-UWP/</A>&nbsp;</LI> <LI id="pragma-line-118"><STRONG>ScrollViewer</STRONG> control - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.scrollviewer</A></LI> <LI id="pragma-line-119">ScrollViewer UX Guidance - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/scroll-controls</A></LI> <LI id="pragma-line-120">Viewbox - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.viewbox</A></LI> </UL> </DIV> Sat, 06 Jun 2020 16:06:55 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/handling-zoom-in-inking-applications/ba-p/1354930 Sebastien Bovo 2020-06-06T16:06:55Z Use the UWP Inking platform as input for advanced scenarios https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471 <DIV id="MainContent"><!-- Markdown Monster Content --> <P>Since almost 5 years, Windows 10 is bringing innovations and powerful usages of devices' capabilities; The <STRONG>Windows Inking platform</STRONG> ✏ is a one great example of natural user interface: With the use of the pen, your computer or tablet becomes a intelligent digital paper! You can free your imagination and draw/paint thanks to apps like <A href="#" target="_blank" rel="noopener">Fresh Paint</A>. Also with <A href="#" target="_blank" rel="noopener">AI services</A> your written words are understood and the corresponding text is produced; your drawings are recognized and converted into geometric shapes!</P> <P>In the Enterprise space, writing and drawing can be a vector for innovation and productivity for users. Let's first go deeper in some usages of the Windows Inking capabilities. We will then explore how Azure AI Services can unlock impressive scenarios.</P> <H2>&nbsp;</H2> <H2 id="the-starting-point-and-docs-"><A id="pragma-line-6" target="_blank"></A>The starting point and docs</H2> <P>The Microsoft docs website gives you all the APIs and controls details to bring you into the Digital Inking journey. Let me give you some pieces of advise for starting:</P> <OL id="pragma-line-8"> <LI id="pragma-line-8">Just open/compile/look at the source code of the <A href="#" target="_blank" rel="noopener">SimpleInk Sample</A> which is part of the Universal Windows official samples. The XAML is straightforward. The C# code is light and easily understandable. It will give you the ability to give a try to inking code in your project with only some copy/paste. The initial controls to use are <UL id="pragma-line-9"> <LI id="pragma-line-9">InkCanvas - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.inkcanvas</A></LI> <LI id="pragma-line-10">InkToolbar - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.inktoolbar</A></LI> </UL> </LI> <LI id="pragma-line-11">Keep an eye on the Windows Ink UX guidelines - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/inking-controls</A></LI> <LI id="pragma-line-12">Bookmark the landing page for Inking interactions - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/design/input/pen-and-stylus-interactions</A></LI> <LI id="pragma-line-13">Refer to the following 2 namespaces <UL id="pragma-line-14"> <LI id="pragma-line-14">Windows.UI.Input.Inking - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Input.Inking</A></LI> <LI id="pragma-line-15">Windows.UI.Xaml.Controls - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls</A> (For Ink* controls)</LI> </UL> </LI> </OL> <P>--</P> <P>&nbsp;</P> <P>With all the documentation mentioned before, you would be easily able to</P> <UL id="pragma-line-20"> <LI id="pragma-line-20">Use the InkCanvas for allowing pen/mouse/touch drawing</LI> <LI id="pragma-line-21">Add a InkToolbar to change the pencil type/size/color</LI> <LI id="pragma-line-22">Use the ruler to draw perfect lines</LI> <LI id="pragma-line-23">Select Inkstrokes, copy/cut/paste them</LI> <LI id="pragma-line-24">Save and load serialized Inkstrokes on disk</LI> </UL> <H2>&nbsp;</H2> <H2 id="the-idea--go-further-of-the-inkstrokes"><A id="pragma-line-26" target="_blank"></A>The idea: Go further of the InkStrokes</H2> <P>The purpose of this blog post is to go deeper on one scenario: <STRONG>Accept Inkstrokes and convert them to shapes!</STRONG> What? Inkstrokes are not good? Do not misunderstand: Strokes are perfect. They are the digital objects representing the strokes that the user created by using the pen/mouse/touch. The idea is to leverage on the drawings to create digital formalized shapes. This way we can produce high fidelity floor plan, measure lengths, areas; we can create vectorial shapes and figures for printing, creating user interfaces, etc.</P> <H2>&nbsp;</H2> <H2 id="the-initial-project-"><A id="pragma-line-30" target="_blank"></A>The initial project</H2> <P>The simplest way to be able to accept inking is to use the InkCanvas control. Create a new empty UWP blank projet and just add the following XAML:</P> <PRE><CODE id="pragma-line-32" class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">InkCanvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt;</SPAN> </CODE></PRE> <P>In the C# code, we just say what type of input we accept for this InkCanvas. Make your choice depending of the requirements of your app (You can change it at anytime by code):</P> <PRE><CODE id="pragma-line-37" class="language-csharp hljs"><SPAN class="hljs-comment">// Initialize the InkCanvas</SPAN> inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch; </CODE></PRE> <P>We are done, you can draw on the whole surface of the app with pen/touch/mouse.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="InitialInkCanvasScreen.png" style="width: 541px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/188156i4A0693FF5DDF17C5/image-size/large?v=v2&amp;px=999" role="button" title="InitialInkCanvasScreen.png" alt="Sample drawing by hands on a UWP Window" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Sample drawing by hands on a UWP Window</span></span></P> <P>&nbsp;</P> <P>The source code is available on GitHub - <A href="#" target="_blank" rel="noopener">https://github.com/sbovo/UWP-Advanced-Inking/tree/master/StrokesToShapes</A></P> <H2>&nbsp;</H2> <H2 id="getting-the-last-inkstroke-drawn-"><A id="pragma-line-51" target="_blank"></A>Getting the last InkStroke drawn ✏</H2> <P>In order to be able to do some actions after each draw, the Ink platform gives us the <EM><STRONG>StrokesCollected</STRONG></EM> event on the <EM><STRONG>InkPresenter</STRONG></EM> object associated with the <EM><STRONG>InkCanvas</STRONG></EM>.</P> <BLOCKQUOTE id="pragma-line-53"> <P><STRONG>Note:</STRONG> The <A href="#" target="_blank" rel="noopener">InkPresenter</A> provides properties, methods, and events for managing the input. That includes the processing and the rendering of the Ink input. It allows us to gain a fined control on the input.</P> </BLOCKQUOTE> <P>Let's add a event handler for the <EM><STRONG>StrokesCollected</STRONG></EM> event:</P> <PRE><CODE id="pragma-line-56" class="language-csharp hljs">inkCanvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected; </CODE></PRE> <P>Then, in this event handler, we just get the latest stroke from the InkPresenter:</P> <PRE><CODE id="pragma-line-62" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">InkPresenter_StrokesCollected</SPAN>(<SPAN class="hljs-params"> Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args</SPAN>)</SPAN> { InkStroke stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Last(); } </CODE></PRE> <H2>&nbsp;</H2> <H2 id="working-with-xaml-shapes-"><A id="pragma-line-71" target="_blank"></A>Working with XAML Shapes</H2> <P>XAML Shapes were first introduced in WPF. If you discover XAML Shapes, the following page gives you a perfect startup: Draw shapes - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/shapes</A>.</P> <P>XAML Shapes are basically vector-based regions in the XAML UI. They provide you the ability to draw not only simple forms like rectangles, circles, ellipses but also any path, Bezier curves or geometric shapes. All are vector's shapes displayed on a XAML Canvas.</P> <P>Let's go back to our goal: We have the last InkStroke and we would like to "replace" it by a nice and friendly XAML Shapes that we could manipulate/move/modify easily by APIs.</P> <P>First, we modify the XAML page in order to add a new Canvas. The one that will contains all XAML Shapes:</P> <PRE><CODE id="pragma-line-79" class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Canvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"ShapesCanvas"</SPAN> /&gt;</SPAN> </CODE></PRE> <P>The entire XAML page is like:</P> <PRE><CODE id="pragma-line-84" class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Grid</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">InkCanvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"inkCanvas"</SPAN> /&gt;</SPAN> <SPAN class="hljs-comment">&lt;!-- Canvas for displaying the "recognized" XAML Shapes --&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Canvas</SPAN> <SPAN class="hljs-attr">x:Name</SPAN>=<SPAN class="hljs-string">"ShapesCanvas"</SPAN> /&gt;</SPAN> <SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Grid</SPAN>&gt;</SPAN> </CODE></PRE> <P>It now like we have two layers, on for drawing on the surface and the other one for displaying/manipulating all the final XAML shapes.</P> <P>We can now do two actions:</P> <OL id="pragma-line-96"> <LI id="pragma-line-96">Create the new <A href="#" target="_blank" rel="noopener">XAML Line</A> based on the properties of the last InkStroke and add this line to the <EM>ShapesCanvas</EM></LI> <LI id="pragma-line-97">Remove the last InkStroke from the <EM>inkCanvas</EM> in order to have a clean display made only by XAML Shapes</LI> </OL> <DIV id="MainContent"> <P>In order to be able to perform these two actions when the user has just finished to draw a line, we use the <EM><STRONG>StrokesCollected</STRONG></EM> event of the <STRONG>InkPresenter</STRONG> object attached to the InkCanvas. We add the following event handler declaration in the constructor of the page:</P> <PRE><CODE id="pragma-line-101" class="language-csharp hljs"><SPAN class="hljs-comment">// When the user finished to draw something on the InkCanvas</SPAN> inkCanvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected; </CODE></PRE> <P>&nbsp;</P> <P>Let's code the 2 actions:</P> </DIV> <PRE><CODE id="pragma-line-99" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">InkPresenter_StrokesCollected</SPAN>(<SPAN class="hljs-params"> Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args</SPAN>)</SPAN> { InkStroke stroke = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Last(); <SPAN class="hljs-comment">// Action 1 = We use a function that we will implement just after to create the XAML Line</SPAN> Line line = ConvertStrokeToXAMLLine(stroke); <SPAN class="hljs-comment">// Action 2 = We add the Line in the second Canvas</SPAN> ShapesCanvas.Children.Add(line); <SPAN class="hljs-comment">// We delete the InkStroke from the InkCanvas</SPAN> stroke.Selected = <SPAN class="hljs-literal">true</SPAN>; inkCanvas.InkPresenter.StrokeContainer.DeleteSelected(); } </CODE></PRE> <H2>&nbsp;</H2> <H2 id="the-conversion--inkstroke--xaml-line"><A id="pragma-line-118" target="_blank"></A>THE "conversion" ⚙: InkStroke → XAML Line</H2> <P>It is just about creating a <EM>Line</EM> (Windows.UI.Xaml.Shapes.Line) object with the initial and final positions of the InkStroke :unamused_face:</img>:grinning_face:</img>:</P> <PRE><CODE id="pragma-line-120" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> Line <SPAN class="hljs-title">ConvertStrokeToXAMLLine</SPAN>(<SPAN class="hljs-params">InkStroke stroke</SPAN>)</SPAN> { <SPAN class="hljs-keyword">var</SPAN> line = <SPAN class="hljs-keyword">new</SPAN> Line(); line.Stroke = <SPAN class="hljs-keyword">new</SPAN> SolidColorBrush(Windows.UI.Colors.Green); line.StrokeThickness = <SPAN class="hljs-number">6</SPAN>; <SPAN class="hljs-comment">// The origin = (X1, Y1)</SPAN> line.X1 = stroke.GetInkPoints().First().Position.X; line.Y1 = stroke.GetInkPoints().First().Position.Y; <SPAN class="hljs-comment">// The end = (X2, Y2)</SPAN> line.X2 = stroke.GetInkPoints().Last().Position.X; line.Y2 = stroke.GetInkPoints().Last().Position.Y; <SPAN class="hljs-keyword">return</SPAN> line; } </CODE></PRE> <P>Here we go. We did it. We can draw on the surface and the final line will only be the XAML Line we added to the second Canvas.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="FinalXAMLShapesCanvas.png" style="width: 537px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/188157iE8FEEB23D75ACBD7/image-size/large?v=v2&amp;px=999" role="button" title="FinalXAMLShapesCanvas.png" alt="XAML Shapes (Line) on a UWP Window" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">XAML Shapes (Line) on a UWP Window</span></span></P> <P>&nbsp;</P> <P>This scenario "converting an Ink stroke to a XAML shape" is the basis for being able to achieve great and complex manipulations. Stay tuned for upcoming blog posts about XAML Shapes manipulations and modifications.</P> <P>&nbsp;</P> <P>As usual, all the source code is available on GitHub - <A href="#" target="_blank" rel="noopener">https://github.com/microsoft/Windows-AppConsult-Samples-UWP/</A></P> <P>&nbsp;</P> <P>Feedback is welcome ;)</img></P> <P>Happy coding on the Windows Platform!</P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="noopener">@sbovo</A> for the AppConsult team.</P> <P>&nbsp;</P> <P>&nbsp;</P> <H2 id="inking-series-articles">Inking series' articles</H2> <P>This article is part of a series exploring concepts about inking and XAML Shapes. Here are all links:</P> <OL id="pragma-line-448"> <LI id="pragma-line-448"> <P data-unlink="true"><STRONG>Use the UWP Inking platform as input for advanced scenarios</STRONG> ⇐ You are here&nbsp;&nbsp;</P> </LI> <LI id="pragma-line-449"> <P data-unlink="true"><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/handling-zoom-in-inking-applications/ba-p/1354930" target="_blank" rel="noopener">Handling zoom in Inking applications</A></P> </LI> <LI id="pragma-line-451"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/turning-to-the-dark-side-of-inking-unprocessedinput/ba-p/1364331" target="_blank" rel="noopener">Turning to the dark side of inking = UnprocessedInput&nbsp;</A></P> </LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/free-your-mind-start-manipulating-xaml-shapes/ba-p/1442899" target="_blank" rel="noopener">Free your mind: Start manipulating XAML Shapes</A></LI> <LI> <DIV id="MainContent"> <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/xaml-shapes-manipulation-level-up/ba-p/1445405" target="_blank" rel="noopener">XAML Shapes manipulation level up</A></P> </DIV> </LI> </OL> <P>&nbsp;</P> <H2 id="references"><A id="pragma-line-151" target="_blank"></A>References</H2> <UL id="pragma-line-152"> <LI id="pragma-line-152"> <P><STRONG>SimpleInk sample</STRONG>: Find examples of customization and extensibility possibilities provided by the Ink controls and APIs - <A href="#" target="_blank" rel="noopener">https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/SimpleInk</A></P> </LI> <LI id="pragma-line-153"> <P><STRONG>XAML Controls Gallery sample</STRONG>: Explore the controls in action - <A href="#" target="_blank" rel="noopener">https://github.com/Microsoft/Xaml-Controls-Gallery</A></P> </LI> <LI id="pragma-line-155"> <P>Windows Ink UX guidelines - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/inking-controls</A></P> </LI> <LI id="pragma-line-156"> <P>Pen interactions and Windows Ink in UWP apps - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/design/input/pen-and-stylus-interactions</A></P> </LI> <LI id="pragma-line-157"> <P>Windows.UI.Input.Inking - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Input.Inking</A></P> </LI> <LI id="pragma-line-158"> <P>Windows.UI.Xaml.Controls - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls</A> (For Ink* controls)</P> <UL id="pragma-line-159"> <LI id="pragma-line-159">InkCanvas - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.inkcanvas</A></LI> <LI id="pragma-line-160">InkToolbar - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.inktoolbar</A></LI> </UL> </LI> <LI id="pragma-line-161"> <P>Windows.UI.Xaml.Shapes - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Shapes</A></P> </LI> <LI id="pragma-line-162"> <P>Draw shapes - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/shapes</A></P> </LI> <LI id="pragma-line-163"> <P>XAML Line - <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Shapes.Line</A></P> </LI> </UL> <!-- End Markdown Monster Content --></DIV> Sat, 06 Jun 2020 16:07:10 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/use-the-uwp-inking-platform-as-input-for-advanced-scenarios/ba-p/1352471 Sebastien Bovo 2020-06-06T16:07:10Z Implements silent auto update feature as far as possible for MSIX packaged apps https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/implements-silent-auto-update-feature-as-far-as-possible-for/ba-p/1334405 <P>MSIX packaged apps can implement auto update feature really easy</P> <P>Just checking 'Enable automatic updates' on create app packages wizard.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_0-1587693341707.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/186370i0388829B50C75FE2/image-size/medium?v=v2&amp;px=400" role="button" title="KazukiOta_0-1587693341707.png" alt="KazukiOta_0-1587693341707.png" /></span></P> <P>And there are a few customize points the automatic updates feature such as:</P> <UL> <LI>ShowPrompt attribute for OnLaunch element: A boolean taht if UI will be shown to the user, when update is available on start up time of the app.&nbsp;</LI> <LI>UpdateBlocksActivation for OnLaunch element: A boolean that determines if the UI shown to the user allows the user to launch the app without taking the update.</LI> <LI>AutomaticBackgroundTask element: Checks for updates in the background every 8 hours independntly of whether the user launched the app. This type of update cannnot show UI.</LI> </UL> <P>Please see the following document to checking all options:</P> <P><A href="#" target="_self">Configure update settings in the App Installer file</A></P> <P>&nbsp;</P> <P>In latest Visual Studio 2019, there is an App Installer item template to configure it.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_0-1587697192793.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/186401iD832DF3323CE32FB/image-size/medium?v=v2&amp;px=400" role="button" title="KazukiOta_0-1587697192793.png" alt="KazukiOta_0-1587697192793.png" /></span></P> <P>You can configure it like below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_1-1587697341207.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/186402i5FAC7C4DC9BDFAE2/image-size/medium?v=v2&amp;px=400" role="button" title="KazukiOta_1-1587697341207.png" alt="KazukiOta_1-1587697341207.png" /></span></P> <P>The above configuration is best configuration to update silently to latest update as far as possible. <BR />Even if there was an update between the background updates every 8 hours, then the app will check update when launching time, and then show confirm dialog for updating.</P> <DIV id="tinyMceEditorKazukiOta_0" class="mceNonEditable lia-copypaste-placeholder">&nbsp;</DIV> <P>If you would like to check updates any timing you want, then it can do using&nbsp;<A href="#" target="_self">Package.CheckUpdateAvailabilityAsync method</A>. After the method is called, when the next launching, if there is an update, then the app will be updated.</P> <P>In case of setting ShowPrompt option, then progress Window will appear, but confirm dialog will not be displayed. In case of not setting ShowPrompt option, then the app will be updated silently.</P> <P>&nbsp;</P> <P>For example, if you would like to check updates when users log into Windows, then you can use <A href="#" target="_self">startup task</A> to call the method. If you don't need any user interface, then you can use following code as startup task.</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using System; using System.Threading.Tasks; using Windows.ApplicationModel; namespace AutoUpdateTask { class Program { static async Task Main(string[] args) { await Package.Current.CheckUpdateAvailabilityAsync(); } } } </LI-CODE> <P>&nbsp;</P> <P>To not see command prompt window, the console app project file's output type is set to WinExe like following:</P> <P>&nbsp;</P> <LI-CODE lang="markup">&lt;Project Sdk="Microsoft.NET.Sdk"&gt; &lt;PropertyGroup&gt; &lt;OutputType&gt;WinExe&lt;/OutputType&gt; &lt;TargetFramework&gt;netcoreapp3.1&lt;/TargetFramework&gt; &lt;RuntimeIdentifiers&gt;win-x86;win-x64&lt;/RuntimeIdentifiers&gt; &lt;Platforms&gt;x86;x64&lt;/Platforms&gt; &lt;/PropertyGroup&gt; &lt;ItemGroup&gt; &lt;PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.18362.2005" /&gt; &lt;/ItemGroup&gt; &lt;/Project&gt;</LI-CODE> <P>&nbsp;</P> <P>And then configure the exe to startup stask, add the definition to Package.appxmanifest, and then startup task registration code to entry point of your main apps:</P> <P>&nbsp;</P> <LI-CODE lang="markup">&lt;desktop:Extension Category="windows.startupTask" Executable="AutoUpdateTask\AutoUpdateTask.exe" EntryPoint="Windows.FullTrustApplication"&gt; &lt;desktop:StartupTask TaskId="AutoUpdateTask" Enabled="true" DisplayName="Auto Update Task" /&gt; &lt;/desktop:Extension&gt; </LI-CODE><LI-CODE lang="csharp">var startupTask = await StartupTask.GetAsync("AutoUpdateTask"); var state = startupTask.State; if (state != StartupTaskState.Enabled) { state = await startupTask.RequestEnableAsync(); } </LI-CODE> <P>&nbsp;</P> <P>If the app update was published, then it is checked when users log into Windows, and then the app would be updated automatically.<BR /><BR /></P> <H2>Conclusion</H2> <P>.appinstaller provides powerful auto update feature, and you can also check updates using Package.CheckUpdateAvairabilityAsync method on any timing you want.</P> <P>In this article, I explained how to add checking update feature as startup task. If you would like to provide updates to users without UI interaction, then it is good approach for now.&nbsp;</P> Fri, 24 Apr 2020 08:25:58 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/implements-silent-auto-update-feature-as-far-as-possible-for/ba-p/1334405 KazukiOta 2020-04-24T08:25:58Z How to use embedded web UI of MSAL.NET on WPF on .NET Core https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-to-use-embedded-web-ui-of-msal-net-on-wpf-on-net-core/ba-p/1315024 <P>Azure AD B2C is a powerful service for providing business-to-customer identity.</P> <P><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/azure/active-directory-b2c/overview</A></P> <P>&nbsp;</P> <P>Of cause, you can also use Azure AD B2C sign feature on WPF on .NET Core. However, at now(April 17, 2020), there are few limitations:</P> <P><A href="#" target="_blank" rel="noopener">https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core</A></P> <P>&nbsp;</P> <OL> <LI>You can't use embedded web browser UI.(Have to use System browsers as default)</LI> <LI>You can't use 'http://localhost'(No port) redirect URL.(The feature will be released soon: <A href="#" target="_blank" rel="noopener">https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/1213</A>)</LI> </OL> <P>In this article, I will try implements custom web ui for embedded web browser UI for WPF on .NET Core.</P> <H3>How to impl?</H3> <P>That's really simple! <BR />Just implements Microsoft.Identity.Client.Extensibility.ICustomWebUi interface. There is a single method:</P> <P>&nbsp;</P> <LI-CODE lang="csharp">Task&lt;Uri&gt; AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken)</LI-CODE> <P>&nbsp;</P> <P>The return value is an URI that has code=CODE parameters.</P> <P>If there are something wrong during authentication flow, then throws MsalExtensionException.</P> <H4>Impl a Browser Window and the interface</H4> <P>To show a custom web UI, create a window that has a WebBrowser control.</P> <P>&nbsp;</P> <LI-CODE lang="markup">&lt;Window x:Class="EmbeddedMsalCustomWebUi.Wpf.Internal.EmbeddedWebUiWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:EmbeddedMsalCustomWebUi.Wpf.Internal" mc:Ignorable="d" WindowStyle="ToolWindow" Loaded="Window_Loaded" Closed="Window_Closed" Title="EmbeddedWebUiWindow" Height="450" Width="800"&gt; &lt;Grid&gt; &lt;WebBrowser x:Name="webBrowser" Navigating="WebBrowser_Navigating" /&gt; &lt;/Grid&gt; &lt;/Window&gt;</LI-CODE> <P>&nbsp;</P> <P>And then, implements the code behind.</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using Microsoft.Identity.Client.Extensibility; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Windows; using System.Windows.Navigation; namespace EmbeddedMsalCustomWebUi.Wpf.Internal { public partial class EmbeddedWebUiWindow : Window { private readonly Uri _authorizationUri; private readonly Uri _redirectUri; private readonly TaskCompletionSource&lt;Uri&gt; _taskCompletionSource; private readonly CancellationToken _cancellationToken; private CancellationTokenRegistration _token; public EmbeddedWebUiWindow( Uri authorizationUri, Uri redirectUri, TaskCompletionSource&lt;Uri&gt; taskCompletionSource, CancellationToken cancellationToken) { InitializeComponent(); _authorizationUri = authorizationUri; _redirectUri = redirectUri; _taskCompletionSource = taskCompletionSource; _cancellationToken = cancellationToken; } private void WebBrowser_Navigating(object sender, NavigatingCancelEventArgs e) { if (!e.Uri.ToString().StartsWith(_redirectUri.ToString())) { // not redirect uri case return; } // parse query string var query = HttpUtility.ParseQueryString(e.Uri.Query); if (query.AllKeys.Any(x =&gt; x == "code")) { // It has a code parameter. _taskCompletionSource.SetResult(e.Uri); } else { // error. _taskCompletionSource.SetException( new MsalExtensionException( $"An error occurred, error: {query.Get("error")}, error_description: {query.Get("error_description")}")); } Close(); } private void Window_Loaded(object sender, RoutedEventArgs e) { _token = _cancellationToken.Register(() =&gt; _taskCompletionSource.SetCanceled()); // navigating to an uri that is entry point to authorization flow. webBrowser.Navigate(_authorizationUri); } private void Window_Closed(object sender, EventArgs e) { _taskCompletionSource.TrySetCanceled(); _token.Dispose(); } } } </LI-CODE> <P>&nbsp;</P> <P>Implements AcquireAuthorizationCodeAsync method using the above window.</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using EmbeddedMsalCustomWebUi.Wpf.Internal; using Microsoft.Identity.Client.Extensibility; using System; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace EmbeddedMsalCustomWebUi.Wpf { /// &lt;summary&gt; /// Provides embedded web ui for WPF on .NET Core. /// The web ui is using WebBrowser control(Trident engine). /// &lt;/summary&gt; public class EmbeddedBrowserWebUi : ICustomWebUi { public const int DefaultWindowWidth = 600; public const int DefaultWindowHeight = 800; private readonly Window _owner; private readonly string _title; private readonly int _windowWidth; private readonly int _windowHeight; private readonly WindowStartupLocation _windowStartupLocation; public EmbeddedBrowserWebUi(Window owner, string title = "Sign in", int windowWidth = DefaultWindowWidth, int windowHeight = DefaultWindowHeight, WindowStartupLocation windowStartupLocation = WindowStartupLocation.CenterOwner) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _title = title; _windowWidth = windowWidth; _windowHeight = windowHeight; _windowStartupLocation = windowStartupLocation; } public Task&lt;Uri&gt; AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource&lt;Uri&gt;(); _owner.Dispatcher.Invoke(() =&gt; { new EmbeddedWebUiWindow(authorizationUri, redirectUri, tcs, cancellationToken) { Owner = _owner, Title = _title, Width = _windowWidth, Height = _windowHeight, WindowStartupLocation = _windowStartupLocation, }.ShowDialog(); }); return tcs.Task; } } } </LI-CODE> <LI-SPOILER>I decided using WPF WebBrowser control because it is easest way to use WebBrowser control on WPF. Other options are using old edge engine or new edge engine(the status is developer preview now).<BR />If you would like to use them, then please replace WebBrowser control to one you want.</LI-SPOILER> <H3>Test</H3> <P>&nbsp;</P> <P>Create an app for WPF client on Azure AD B2C tenant as public client app and check redirect URL.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_1-1587107372027.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/184932i903D52D75F9CAC2A/image-size/large?v=v2&amp;px=999" role="button" title="KazukiOta_1-1587107372027.png" alt="KazukiOta_1-1587107372027.png" /></span></P> <P>&nbsp;</P> <P>And create an another app on B2C tenant, and export an API from the app, then add the permission from the client app.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_2-1587107525419.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/184933iA115224C276B2C2A/image-size/medium?v=v2&amp;px=400" role="button" title="KazukiOta_2-1587107525419.png" alt="KazukiOta_2-1587107525419.png" /></span></P> <P>And If you haven't created sign in user flow on the tenant, then create it.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_3-1587107952035.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/184934iAC7E31A0AB81B8E6/image-size/medium?v=v2&amp;px=400" role="button" title="KazukiOta_3-1587107952035.png" alt="KazukiOta_3-1587107952035.png" /></span></P> <P>Collect following items to use WPF app:</P> <UL> <LI>Application ID(Client ID) (GUID)</LI> <LI>Tenant ID (GUID)</LI> <LI>Redirect URI: it is on the Authentication page on the B2C portal.</LI> <LI>Azure AD B2C Authority: <A href="#" target="_blank" rel="noopener">https://{tenant</A>&nbsp;name}.b2clogin.com/tfp/{tenant name}.onmicrosoft.com/{user flow name}.</LI> <LI>Scope: It is an URL (<A href="#" target="_blank" rel="noopener">https://{tenant name}.onmicrosoft.com/{guid}/{name</A>}) that is able to get at API permissions page of another app.</LI> </UL> <P>And then create a WPF App(.NET Core) project, and create an IPublicClientApplication instance on Startup event of App class.</P> <P>&nbsp;</P> <LI-CODE lang="csharp">PublicClientApplication = PublicClientApplicationBuilder.Create("{your client id}") .WithRedirectUri("{your redirect uri}") .WithTenantId("your tenant id") .WithB2CAuthority("{your azure ad b2c authority}") .Build(); </LI-CODE> <P>&nbsp;</P> <P>The last step! Use EmbeddedBrowserWebUi class that implemented on this article with AcquireTokenInteractive method.</P> <P>&nbsp;</P> <LI-CODE lang="csharp"> var r = await PublicClientApplication .AcquireTokenInteractive(new[] { "{your scope}" }) .WithCustomWebUi(new EmbeddedBrowserWebUi(this)) // here .ExecuteAsync(); </LI-CODE> <P>&nbsp;</P> <P>It works as below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="signinflow.gif" style="width: 784px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/184936iE557CE3C8DE2DBCD/image-size/large?v=v2&amp;px=999" role="button" title="signinflow.gif" alt="signinflow.gif" /></span></P> <P>&nbsp;</P> <H4>Completed source code</H4> <P>The custom web ui and test app codes are on following github repo:</P> <P><A href="#" target="_blank" rel="noopener">https://github.com/runceel/EmbeddedMsalCustomWebUi.Wpf</A></P> <P>&nbsp;</P> <P>If you would like to try EmbeddedCustomWebUi class on your code, then you can get it from NuGet:</P> <P><A href="#" target="_blank" rel="noopener">https://www.nuget.org/packages/EmbeddedMsalCustomWebUi.Wpf/</A></P> <P>&nbsp;</P> <PRE><FONT color="#FF0000"><STRONG>Important:</STRONG></FONT><BR /><FONT color="#FF0000">This is a just sample code. It is not tested for production.</FONT></PRE> <P>&nbsp;</P> Fri, 17 Apr 2020 13:18:40 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-to-use-embedded-web-ui-of-msal-net-on-wpf-on-net-core/ba-p/1315024 KazukiOta 2020-04-17T13:18:40Z Optimize your Windows Desktop Application for Windows 10 https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/optimize-your-windows-desktop-application-for-windows-10/ba-p/1305449 <P>As AppConsult engineers, we work with a variety of key partners/customers to unblock their Windows 10 opportunities and accelerate feature adoption of Windows Enterprise Developer technologies. We do this through the <STRONG>Developer Platform TAP program</STRONG> which is managed directly by the product group. With this work we setup light house partners that can inspire other companies to use our technology.</P> <P>&nbsp;</P> <P>Based on the feedback provided from our TAP partners in a recent survey, we chose the following three most requested topics for this webcast. The idea is to take advantage of Windows 10 capabilities for your existing Windows Desktop assets:</P> <OL> <LI>Modernize the UI and increase the performance of WinForms and WPF Application with<FONT color="#800080"><STRONG> .NET Core 3.0</STRONG></FONT> (by Mike Francis)</LI> <LI>Ease your .NET Desktop Development and Universal Windows Platform development with <STRONG><FONT color="#0000FF">Win UI 3.0</FONT></STRONG> (by myself)</LI> <LI>Improve the efficiency and reliability of app installation and distribution and provide Continuous Delivery with <STRONG><FONT color="#FF6600">Azure DevOps &amp; MSIX</FONT></STRONG> (by Matteo Pagani)</LI> </OL> <P>Enjoy the webcast!</P> <P>Sebastien.</P> <P>&nbsp;</P> <P><LI-VIDEO vid="https://youtu.be/xw9iUzIgHxs" align="center" size="medium" width="400" height="225" uploading="false" thumbnail="https://i.ytimg.com/vi/xw9iUzIgHxs/hqdefault.jpg" external="url"></LI-VIDEO>&nbsp;</P> Tue, 14 Apr 2020 13:42:44 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/optimize-your-windows-desktop-application-for-windows-10/ba-p/1305449 Sebastien Bovo 2020-04-14T13:42:44Z How to host a live streaming with multiple participants using OBS Studio and Skype https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-to-host-a-live-streaming-with-multiple-participants-using/ba-p/1291745 <P>This time I won't blog about development content, but I wanted to share a solution to a problem that my team has faced in the latest days for which I wasn't able to find much documentation.</P> <P>&nbsp;</P> <P>A little bit of context: as you know, conferences and events play a big role in AppConsult activities and it's one of the experiences that we value most of our job. Unfortunately, due to the COVID-19 situation, they're also one of the first things that have been shutdown, since mass gatherings are one of the most dangerous things to do during a pandemic. As such, we have decided to try a new way to reach developers and communities, by launching a new vlog (or a video podcast, as you prefer). In the next weeks we're going to live stream short episodes (20-30 minutes), hosted by our team and with various guests, to share our experience with many of the technologies we work with, either from the Windows and the Azure side: MSIX, WinUI, Azure Functions, .NET Core 3.0, React Native are just a few examples.</P> <P>&nbsp;</P> <P>As such, we have started to setup an infrastructure to support this effort (based on<SPAN>&nbsp;</SPAN><A href="#" target="_blank">OBS Studio</A><SPAN>&nbsp;</SPAN>and Skype), so that we could stream the episodes on Twitch, Mixer and YouTube and also record them, to make them available for anyone who can't follow them live.</P> <P>&nbsp;</P> <P>However, our first test of the setup failed miserably. During the test call we started to see lot of issues related to echoes and "double audio" when the Skype call was hosting 3 or more people. This was a big blocker for us, because we were planning to have multiple hosts and guests in the same episode. After some tests and investigations, we finally found a solution. The goal of this post is to explain you how you can enable a live streaming with multiple Skype participants using OBS Studio.</P> <P>&nbsp;</P> <H3 id="setting-up-the-environment">Setting up the environment</H3> <P>When you want to have multiple remote people in the same streaming, the best combination is OBS Studio with Skype. OBS Studio is one of the most popular broadcasting applications, used by millions of streamers all around the world. I won't go through all the steps on how to setup OBS Studio. It would be out of scope for this post, I'm giving for granted that you already know the basics on how to use this streaming platform. There are tons of tutorials on Internet, including<SPAN>&nbsp;</SPAN><A href="#" target="_blank">the official documentation</A>. For the purpose of this post, it's important to know just that OBS Studio is one of the best (free) software on the market to turn your machine into a streaming studio. You can setup multiple scenes and each of them can contain different content: your webcam feed, screen sharing, videos, pictures, etc. During the streaming you can switch from one scene to another, in case you need to have different layouts based on the situation: for example, you may want to have a scene only with the camera feed; one with the camera feed and screen sharing; etc. In the end, OBS Studio can connect with the most common streaming platforms (Mixer, Twitch, YouTube, etc.) so that you can directly stream your content to a worldwide audience.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ObsStudio.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183049i8BF26708B7283BE4/image-size/large?v=v2&amp;px=999" role="button" title="ObsStudio.png" alt="ObsStudio.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Skype doesn't need any introduction, since it's one of the most popular communication software in the market. The biggest advantage of Skype is that it supports a technology called&nbsp;</SPAN><A href="#" target="_blank">NDI</A><SPAN>, which allows to handle every person connected to the call as a separate feed, that you can manipulate in your favorite broadcasting tool. Did you ever wonder why, despite the rise of other platforms like Zoom, Skype is still the most widely used communication software when it comes to TV productions, like talk shows? Skype is the only free application on the market which supports NDI, which allows TV engineers to take the video feed and incorporate it in the tools they use to handle the TV broadcast. Thanks to this feature, we can achieve the same goal with OBS Studio, allowing us to incorporate the video feed of the various people connected to the call into our scenes. In the image below you can see the final goal we wanted to achieve: not just streaming a Skype call, but having a more effective and good-looking render, thanks to the awesome graphic that my brother has designed.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="PodcastScreenshot.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183050iF9F3DAED7F5DA697/image-size/large?v=v2&amp;px=999" role="button" title="PodcastScreenshot.png" alt="PodcastScreenshot.png" /></span></SPAN></P> <P>&nbsp;</P> <P>These are the tools you need to achieve this goal:</P> <UL> <LI><A href="#" target="_blank">OBS Studio</A></LI> <LI><A href="#" target="_blank">Skype for Desktop</A>. You can't use the version which comes built-in with Windows 10, since it doesn't include NDI support.</LI> <LI><A href="#" target="_blank">obs-ndi</A>. This is a tool which installs the NDI runtime and the OBS plugin, which is required to add a NDI source to your scenes.</LI> </UL> <H3 id="setting-up-skype">Setting up Skype</H3> <P>Once you have installed Skype for desktop, open it and login with your Microsoft Account. Then open the Settings (you can find them by clicking on the three dots near your profile name).</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Settings.png" style="width: 570px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183051iDADB1C2EB2F108EC/image-size/large?v=v2&amp;px=999" role="button" title="Settings.png" alt="Settings.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Move to the&nbsp;</SPAN><STRONG>Calling</STRONG><SPAN>&nbsp;section and choose&nbsp;</SPAN><STRONG>Advanced</STRONG><SPAN>:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Advanced.png" style="width: 686px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183052i5055141759662092/image-size/large?v=v2&amp;px=999" role="button" title="Advanced.png" alt="Advanced.png" /></span></SPAN></P> <P>&nbsp;</P> <P><SPAN>You will find a section called&nbsp;<STRONG>Content Creators</STRONG>, with the option to turn on the NDI support:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NdiSettings.png" style="width: 686px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183053iC27518F59497F931/image-size/large?v=v2&amp;px=999" role="button" title="NdiSettings.png" alt="NdiSettings.png" /></span></SPAN></P> <P>&nbsp;</P> <P><SPAN>That's it. Now you'll need to start a call with all the people that you want to include in your streaming. They won't need to install anything or turn on NDI support as well in Skype. It's important that just the machine which will be use for streaming runs Skype with NDI support enabled.</SPAN></P> <P>&nbsp;</P> <H3 id="setting-up-obs-studio">Setting up OBS Studio</H3> <P>Once the call is up &amp; running, you can open OBS Studio. If you have installed properly the plugin (and rebooted the machine), when you click on the + sign in the<SPAN>&nbsp;</SPAN><STRONG>Sources</STRONG><SPAN>&nbsp;</SPAN>section you will find a new item called<SPAN>&nbsp;</SPAN><STRONG>NDI source</STRONG>:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NdiSourceObsStudio.png" style="width: 672px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183054iBE2D58F2A78A5E44/image-size/large?v=v2&amp;px=999" role="button" title="NdiSourceObsStudio.png" alt="NdiSourceObsStudio.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Once you have created a new source, you will see a panel to configure all the options.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NdiSourceSetup.png" style="width: 481px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183055iFDDA3961CCD95760/image-size/large?v=v2&amp;px=999" role="button" title="NdiSourceSetup.png" alt="NdiSourceSetup.png" /></span></SPAN></P> <P>&nbsp;</P> <P>The most important one is<SPAN>&nbsp;</SPAN><STRONG>Source name</STRONG>, which is a dropdown that will list all the NDI sources that are detected from Skype. Each source will be prefixed with the name of your computer, followed by the<SPAN>&nbsp;</SPAN><STRONG>(Skype)</STRONG><SPAN>&nbsp;</SPAN>keyword. Typically, you will find the following sources:</P> <P>&nbsp;</P> <UL> <LI><STRONG>Skype - (Local)</STRONG><SPAN>&nbsp;</SPAN>refers to the local video feed. If you have initiated the Skype meeting from your machine, this is the one you have to use for getting your audio and video.</LI> <LI><STRONG>Skype - Active speaker</STRONG><SPAN>&nbsp;</SPAN>always contains the feed of the speaker who is actively speaking. Unless you need a special configuration, you will hardly use it. The typical scenario, in fact, is to place each person included in the call in a specific area of the scene. However, it could be useful, for example, if you have a scene with a video or a screen sharing session and you want the NDI source to always display the person who is talking, since there isn't enough space to include everyone.</LI> <LI><STRONG>Skype - live</STRONG>, followed by the Skype id of the user. You will see multiple sources like this, one for each person involved in the call (except yourself).</LI> </UL> <P>Once you have selected the source you need, press OK. A new item will be added to the scene and, if everything goes well, you should see the video feed of the selected person. Now you can drag and drop it around and place it in the area which fits best your scene. For example, in my case, the graphic created by my brother has 3 dedicated slots for the video feeds:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Overlay.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183056iAE8894C259B5C5B7/image-size/large?v=v2&amp;px=999" role="button" title="Overlay.png" alt="Overlay.png" /></span></P> <P>&nbsp;</P> <P>These are the three areas where I placed the three NDI sources.</P> <P>&nbsp;</P> <P><STRONG>Tip</STRONG>: sometimes you may see that, after an event (like one of the people in the call changing the camera or turning off or on the video feed), the size of the area in the scene changes. To avoid this behavior you can follow<SPAN>&nbsp;</SPAN><A href="#" target="_blank">the tip provided by the Skype team</A>:</P> <P>&nbsp;</P> <OL> <LI>Right-click the NDI source, and then select Transform</LI> <LI>Select Edit Transform.</LI> <LI>Change the Bounding Box Type to Scale inner bounds.</LI> </OL> <H3 id="solving-the-echo-problem">Solving the echo problem</H3> <P>That's it! Now you should have in your scene the video and audio feed coming from each people connected to the Skype call. Since they are made available to OBS Studio as different sources, you are free to move them, resize them, rotate them, etc. It's a much more powerful approach than using screen sharing to share the content of the Skype application. Everything looks great. You're ready to start the show! You press the<SPAN>&nbsp;</SPAN><STRONG>Start streaming</STRONG><SPAN>&nbsp;</SPAN>button, you start the live show but... after a few seconds you start to receive lot of complains from your viewers! The reason is that they're going to hear a sort of echo or "double audio", like if the voice track is duplicated, but with a delay that makes it look like an echo.</P> <P>&nbsp;</P> <P>After a bit of investigation, we found out that the reason is that Skype injects into each NDI source not just the video, but also the whole audio feed. This means that, if you have 3 people connected to the call, each of them will push to OBS studio not just their voice, but the whole audio track, including the voice of the other 2 participants. This is the cause of the echo effect: every person in the call is basically saying the same sentence two or three times but, due to the network delay, they aren't perfectly in sync.</P> <P>&nbsp;</P> <P>The way to solve this is to make sure that OBS Studio outputs to the audio stream only a single NDI source. All the others can be turned off, since a single NDI source will contain the voice of all the people involved in the call. In order to do this, click on<SPAN>&nbsp;</SPAN><STRONG>Edit</STRONG><SPAN>&nbsp;</SPAN>in OBS Studio and choose<SPAN>&nbsp;</SPAN><STRONG>Advanced Audio Properties</STRONG>. You will see a window like this:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AdvancedAudioEditor.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/183057i81DDBBD5E5632614/image-size/large?v=v2&amp;px=999" role="button" title="AdvancedAudioEditor.png" alt="AdvancedAudioEditor.png" /></span></P> <P>&nbsp;</P> <P>The tool will list all the active audio sources in the scene, including your desktop audio, your microphone and each NDI source. Take a look at the<SPAN>&nbsp;</SPAN><STRONG>Tracks</STRONG><SPAN>&nbsp;</SPAN>column. You can use it to specify on which track you want to assign an audio source. This setting is important if you're planning to record your stream, since it will allow OBS Studio to store the various audio sources in different tracks. This way you will get more flexibility if you're planning to do some post-processing of the video. However, it's useful also in the streaming scenario. Track 1 is the one used by OBS Studio during streaming. Every audio source which is part of Track 1 will be also streamed to your viewers. As such, it's enough to uncheck the Track 1 in all the sources, except one NDI source, to solve our problem. You can see an example in the screenshot above: all the Track 1 checkboxes are turned off, except for the one related to<SPAN>&nbsp;</SPAN><STRONG>Matteo</STRONG>, which is the NDI source connected to my local Skype feed.</P> <P>&nbsp;</P> <P>That's it! Now your viewers will be able to enjoy your live stream with multiple Skype guests without any echo problem.</P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>If you're planning to start a live stream and to have multiple remote guests, OBS Studio and Skype are a great couple. However, as soon as you start the streaming, you will hit some audio issues, caused by the way how Skype works. In this post you have learned how to handle this situation and how to setup OBS in the proper way. Now that your scene is setup, all the settings will be stored. Don't worry if you will see empty areas instead of the NDI sources when you will reopen the application. This is expected if the Skype call isn't active, since there won't be any live feed. However, OBS Studio has stored the information about the Skype account so, as long as you reuse always the same Microsoft Accounts, you're good to go. For this reason, if you're planning to invite guests to your live streaming, I suggest you to create a dedicated Microsoft Account and to ask to your guest to connect to the call using that one. This way, you won't have to reconfigure the scene every time you invite a different guest.</P> <P>&nbsp;</P> <P>Happy streaming! We'll get back shortly with all the information to follow our new AppConsult podcast!</P> Wed, 08 Apr 2020 12:59:43 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-to-host-a-live-streaming-with-multiple-participants-using/ba-p/1291745 Matteo Pagani 2020-04-08T12:59:43Z Add TypeScript support to React Native for Windows https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/add-typescript-support-to-react-native-for-windows/ba-p/1136376 <P>React Native is an interesting technology!! Theer are many great articles our team blog.</P> <UL> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/getting-started-with-react-native-for-windows/ba-p/912093" target="_self">Getting started with React Native for Windows</A></LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-a-react-native-module-for-windows/ba-p/1067893" target="_self">Building a React Native module for Windows</A></LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_self">Create a CI/CD pipeline for a React Native for Windows application</A></LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/react-native-for-windows-and-native-modules-how-to-add-ci-cd-to/ba-p/1085473" target="_self">React Native for Windows and native modules: how to add CI/CD to your project</A></LI> </UL> <P>In this article, I will explain to add TypeScript support to a RN4W project.</P> <H2>Create a new RN4W project(JS)</H2> <P>First, we create a new RN4W project in JavaScript. The completely steps are writtn on the official GitHub repository.</P> <P style="padding-left: 30px;"><STRONG>Consuming react native windows</STRONG><BR /><A href="#" target="_blank" rel="noopener">https://github.com/microsoft/react-native-windows/blob/master/vnext/docs/ConsumingRNW.md</A></P> <P>After doing the steps, we will see following:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_0-1580982883306.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/169362iC466D6BC8E25F91C/image-size/large?v=v2&amp;px=999" role="button" title="KazukiOta_0-1580982883306.png" alt="KazukiOta_0-1580982883306.png" /></span></P> <H2>Add TypeScript support</H2> <P>There is a official document that is "Adding TypeScript to an Existing Project."<BR /><A href="#" target="_blank" rel="noopener">https://facebook.github.io/react-native/docs/typescript#adding-typescript-to-an-existing-project</A></P> <P>The steps also works for React Native for Windows. So let's type following command:</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">yarn add typescript @types/jest @types/react @types/react-native @types/react-test-renderer</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><SPAN>And adding tsconfig.json like below:</SPAN></P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">{ "compilerOptions": { "allowJs": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "isolatedModules": true, "jsx": "react", "lib": ["es6"], "moduleResolution": "node", "noEmit": true, "strict": true, "target": "esnext", "resolveJsonModule": true }, "exclude": [ "node_modules", "babel.config.js", "metro.config.js", "jest.config.js" ] }</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>There is a different line from the original document. I added "resolveJsonModule": true because in the React Native template app is using import JSON file directly.</P> <P>And adding jest.config.js like below. This is a same as the original document.</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">module.exports = { preset: 'react-native', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], };</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>The next, change the file extensions other than index.js from .js to .tsx . App.js is a target file.</P> <P>After that, you will be able to launch the app like below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_0-1580983915027.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/169368i385E7D18AEC2FC31/image-size/large?v=v2&amp;px=999" role="button" title="KazukiOta_0-1580983915027.png" alt="KazukiOta_0-1580983915027.png" /></span></P> <P>But there is a compile error in App.tsx yet. Adding a following type declare to above of App component definition to fix the error.</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">declare var global: {HermesInternal: null | {}};</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>If you remove global.HermesInternal from App.tsx, then you can remove the row.</P> <P>After that, you can get intellisence everywhere.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_1-1580984801768.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/169370i90B75D707FC7C33B/image-size/large?v=v2&amp;px=999" role="button" title="KazukiOta_1-1580984801768.png" alt="KazukiOta_1-1580984801768.png" /></span><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="KazukiOta_0-1580984776787.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/169369i037576D4A28BFEDF/image-size/large?v=v2&amp;px=999" role="button" title="KazukiOta_0-1580984776787.png" alt="KazukiOta_0-1580984776787.png" /></span></P> <P>Happy type safe coding!!</P> Tue, 19 May 2020 07:26:46 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/add-typescript-support-to-react-native-for-windows/ba-p/1136376 KazukiOta 2020-05-19T07:26:46Z Building installers for your Windows applications with Advanced Installer and Azure DevOps https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-installers-for-your-windows-applications-with-advanced/ba-p/1142985 <P>If you follow my activities, like the articles I publish on this blog or<SPAN>&nbsp;</SPAN><A href="#" target="_blank">the recent book I've published about MSIX</A>, you'll know that one of the reasons why I like MSIX from a developer perspective is that it makes really easy to enable a CI/CD pipeline for Windows desktop applications. Thanks to<SPAN>&nbsp;</SPAN><A href="#" target="_blank">the Windows Application Packaging Project</A>, we can easily automate the creation of a MSIX package simply by adding it to our solution and running a build. And thanks to features like<SPAN>&nbsp;</SPAN><A href="#" target="_blank">App Installer</A>, we can easily deploy the generated MSIX package to a website and support automatic updates without changing the code or having to setup your own service.</P> <P>&nbsp;</P> <P>However, in many cases you may already have an installer definition created with a 3rd party tool. Popular authoring tools, like<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Advanced Installer</A>,<SPAN>&nbsp;</SPAN><A href="#" target="_blank">InstallShield</A><SPAN>&nbsp;</SPAN>or<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Wix</A>, are able to generate a MSIX package out from an installer project, making easier to reuse the work you may already have done to generate MSI installers in the past. Additionally, thanks to these tools, you have the opportunity to use the same project to generate at the same time a MSI and a MSIX, helping you to support customers who might not have migrated yet to Windows 10 and who are unable to use<SPAN>&nbsp;</SPAN><A href="#" target="_blank">MSIX Core</A><SPAN>&nbsp;</SPAN>(for example, because it's a consumer application).</P> <P>&nbsp;</P> <P>What about enabling a CI/CD pipeline in this scenario? In this blog post we're going to see an example on how to achieve this task with Advanced Installer. Why did I choose this product? Well, other than because it's a really good software, the Advanced Installer team has created a task for Azure DevOps which is able to build an Advanced Installer project stored on the repository. And, most of all, the task is able to download the most recent version of Advanced Installer and install it on the machine without requiring any user intervention. This means that we are not forced to create a self-hosted agent where to manually install the tool, but we can leverage the built-in agents provided by Azure DevOps.</P> <P>&nbsp;</P> <P>Let's start!</P> <P>&nbsp;</P> <H3 id="the-advanced-installer-project">The Advanced Installer project</H3> <P>As a starting point, I will use a simple Windows Forms application called<SPAN>&nbsp;</SPAN><A href="#" target="_blank">MyEmployees</A>,<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/msix-how-to-copy-data-outside-the-installation-folder-during-the/ba-p/1133602" target="_blank">which I have used also in other posts</A>. However, the main difference is that, this time, the solution doesn't have a Windows Application Packaging Project. We're going to use Advanced Installer to create a MSIX package, together with a MSI. In a real scenario probably you already have the setup created with Advanced Installer but, to understand better how it works, let's create a new one.</P> <P>&nbsp;</P> <P>First you need to<SPAN>&nbsp;</SPAN><A href="#" target="_blank">download and install</A><SPAN>&nbsp;</SPAN>the latest Advanced Installer version. It's a paid product, but it offers a 30 days trial and also a free tier. Once you have launched it, you will find in the<SPAN>&nbsp;</SPAN><STRONG>Templates</STRONG><SPAN>&nbsp;</SPAN>section one called<SPAN>&nbsp;</SPAN><STRONG>Visual Studio Application</STRONG>, which is able to connect directly to a Visual Studio solution, like in our case.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VisualStudioApplication.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168300iD604D83E0BE0C988/image-size/large?v=v2&amp;px=999" role="button" title="VisualStudioApplication.png" alt="VisualStudioApplication.png" /></span></P> <P>&nbsp;</P> <P>First you will be asked the name and the publisher of your application, followed by the distribution type. For the moment choose<SPAN>&nbsp;</SPAN><STRONG>MSI setup file</STRONG>. We're going to add the MSIX definition later. In the next step you will be asked where to save the project, using a file with .aip extension. You will need to add this file in a folder of your project, since we'll need to commit it to our repository, in order to use the Azure DevOps task. In my case, I've created a folder called<SPAN>&nbsp;</SPAN><STRONG>Projects</STRONG><SPAN>&nbsp;</SPAN>inside the solution. The project output folder will be automatically changed to point to the same location, but I suggest you to choose a different one, outside the repository. This folder, in fact, will contain the various artifacts (like the generated MSI or MSIX package) and we don't want to include them in the source code.</P> <P>&nbsp;</P> <P>As next step, you must choose the Visual Studio solution which contains your project. In my case, I've chosen the<SPAN>&nbsp;</SPAN><STRONG>MyEmployees.sln</STRONG><SPAN>&nbsp;</SPAN>file. Advanced Installer will analyze the solution and it will ask you which configurations you want to import. Feel free to choose the ones that make sense for your project. In my case, for example, I want to distribute only the 64 bit version, built in Release mode:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="BuildConfiguration.png" style="width: 503px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168301iD355EF80AD9C892F/image-size/large?v=v2&amp;px=999" role="button" title="BuildConfiguration.png" alt="BuildConfiguration.png" /></span></P> <P>&nbsp;</P> <P>In the next step you will be asked to select which files, among the ones that are created as build output, belong to the application and must be included in the installer. The section will be split in two categories:</P> <P>&nbsp;</P> <UL id="pragma-line-27"> <LI id="pragma-line-27"><STRONG>Output files</STRONG>, which are the files created in the bin folder as part of the build process.</LI> <LI id="pragma-line-28"><STRONG>Reference files</STRONG>, which are the files that are referenced by your projects (for example, 3rd party libraries).</LI> </UL> <P>In my case, I'm going to include all the output files, plus the reference files coming from NuGet, like JSON.net or System.Data.SQLite. I don't need to include the references related to the .NET Framework, since they are already installed on the machine.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="DetectedFiles.png" style="width: 666px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168302iDDBB6098F72B1F9B/image-size/large?v=v2&amp;px=999" role="button" title="DetectedFiles.png" alt="DetectedFiles.png" /></span></P> <P>&nbsp;</P> <P><SPAN>In the next step, you will need to select the main executable of your application. Advanced Installer should be able to automatically detect it, especially if it's a scenario like mine where the application is composed by a single executable. This information will be used to configure the shortcuts on the desktop and/or the Start Menu.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Shortcuts.png" style="width: 666px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168303i7D057B874062033C/image-size/large?v=v2&amp;px=999" role="button" title="Shortcuts.png" alt="Shortcuts.png" /></span></SPAN></P> <P>&nbsp;</P> <P>All the other steps are optional and apply mostly to the MSI setup: you can choose if you want to launch the application after having it installed; you can customize the UI of the installer; you can choose the languages you support; you can add a license agreement that will be displayed during the setup. After you have finished the wizard, Advanced Installer will bring you to the main UI of the application, where you can further customize the project. The tool offers tons of options: you can add support to services or custom actions, you can customize the manifest for the MSIX version, etc.</P> <P>&nbsp;</P> <P>We're going to keep it simple, so we won't add any special configuration. However, there's a setting that is very important. Move to the<SPAN>&nbsp;</SPAN><STRONG>Files and Folders</STRONG><SPAN>&nbsp;</SPAN>section, which displays what and where the installer will copy on the user's machine. You will see a folder called<SPAN>&nbsp;</SPAN><STRONG>Application folder</STRONG>, which is the location where the files which compose the application will be copied. However, you might see that not all the files are properly included.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FilesAndFolders.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168304i9ABE3A74FA2BDE82/image-size/large?v=v2&amp;px=999" role="button" title="FilesAndFolders.png" alt="FilesAndFolders.png" /></span></P> <P>&nbsp;</P> <P>Additionally, you will notice that if you update the source code of your application and you produce a new build, it won't be automatically picked up. To solve this problem we need to setup a sync between the Visual Studio project and the Advanced Installer project, so that during the CI pipeline we can produce a MSI / MSIX which reflects the most recent version.</P> <P>&nbsp;</P> <P>To achieve this goal we must enable the sync feature. Right click on the<SPAN>&nbsp;</SPAN><STRONG>Application Folder</STRONG><SPAN>&nbsp;</SPAN>in the tree and choose<SPAN>&nbsp;</SPAN><STRONG>Properties</STRONG>. Then move to the<SPAN>&nbsp;</SPAN><STRONG>Synchronize</STRONG><SPAN>&nbsp;</SPAN>tab and click on<SPAN>&nbsp;</SPAN><STRONG>Synchronize content with folder from disk</STRONG>. As source folder, you must specify the output of the build in the bin folder of your Visual Studio project. For example, since in my case I'm compiling the application in Release mode, I'm picking up the<SPAN>&nbsp;</SPAN><CODE>MyEmployees\bin\Release</CODE><SPAN>&nbsp;</SPAN>folder. Once you press Ok, you will be asked if you want to remove the files which are already included. Press<SPAN>&nbsp;</SPAN><STRONG>Yes</STRONG>. You will notice now that all the files are correctly included:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="SyncEnabled.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168305i521F3C884DFFB6A3/image-size/large?v=v2&amp;px=999" role="button" title="SyncEnabled.png" alt="SyncEnabled.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Additionally, they're kept in sync with the folder. This means that, whenever you're going to launch a new Visual Studio build, the updated executable and DLLs will be copied over to the Advanced Installer project.</SPAN></P> <P>&nbsp;</P> <H3 id="test-the-msi-generation">Test the MSI generation</H3> <P>Before working on the Azure DevOps side, let's see if the MSI is created successfully. Move to the<SPAN>&nbsp;</SPAN><STRONG>Builds</STRONG><SPAN>&nbsp;</SPAN>section, where you will find a definition called<SPAN>&nbsp;</SPAN><STRONG>DefaultBuild</STRONG>. Right click on it and choose<SPAN>&nbsp;</SPAN><STRONG>Build</STRONG>. If everything goes well, you will find in the Project Output folder you have set during the wizard the MSI. If you try to install it you will get some warnings because the file isn't signed with a valid certificate. Advanced Installer supports signing as part of the build process, but for the moment we're going to keep it unsigned. We'll perform the signing directly on Azure DevOps.</P> <P>&nbsp;</P> <H3 id="create-a-msix-package">Create a MSIX package</H3> <P>Now that we have a project up &amp; running, generating a MSIX package is really simple. We just need to move to the<SPAN>&nbsp;</SPAN><STRONG>Builds</STRONG><SPAN>&nbsp;</SPAN>section and choose<SPAN>&nbsp;</SPAN><STRONG>MSIX / APPX Build</STRONG>. A new build definition will be created, called<SPAN>&nbsp;</SPAN><STRONG>Build_MSIX_APPX</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="MSIXSettings.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168308iD1CF80BCC8DCD98F/image-size/large?v=v2&amp;px=999" role="button" title="MSIXSettings.png" alt="MSIXSettings.png" /></span></P> <P>&nbsp;</P> <P><SPAN>From the page you will be able to set different options related to MSIX packaging, like the minimum Windows 10 version you want to target or the generation of an App Installer file as part of the process. If you want to try it, just right click on this build definition and choose&nbsp;</SPAN><STRONG>Build</STRONG><SPAN>. At the end of the process, you will get in the project output folder a MSIX package. However, in this case you will be completely blocked from installing it, since we haven't signed it. And, as you know, unsigned MSIX packages can't be installed. But don't worry, we're going to do this as well on Azure DevOps.</SPAN></P> <P>&nbsp;</P> <H3 id="putting-everything-under-source-control">Putting everything under source control</H3> <P>Now our project should look like this:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ProjectFiles.png" style="width: 386px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168309i23965C25F29BF44E/image-size/large?v=v2&amp;px=999" role="button" title="ProjectFiles.png" alt="ProjectFiles.png" /></span></P> <P>&nbsp;</P> <P><SPAN>The&nbsp;</SPAN><STRONG>MyEmployees</STRONG><SPAN>&nbsp;folder contains the Windows Forms project, while the&nbsp;</SPAN><STRONG>Projects</STRONG><SPAN>&nbsp;one includes the Advanced Installer setup we have just created. This is the folder you need to make sure to include in your repository on GitHub, Azure Repos or whatever source control provider you prefer. We need to keep the Advanced Installer project together with the source code of the project.</SPAN></P> <P>&nbsp;</P> <H3 id="setup-the-pipeline">Setup the pipeline</H3> <P>Now that we have everything we need, we can start working on the pipeline. However, first, we need to install the task by Advanced Installer on our Azure DevOps account.<SPAN>&nbsp;</SPAN><A href="#" target="_blank">This is the one you need</A>, called<SPAN>&nbsp;</SPAN><STRONG>Advanced Installer Build</STRONG>. The company provides also a task called<SPAN>&nbsp;</SPAN><STRONG>Advanced Installer Tool Installer,</STRONG><SPAN>&nbsp;</SPAN>which takes care of installing Advanced Installer on the hosted agent. However, it isn't really needed because the Build task can do it as well.</P> <P>Once you have installed it, you can move to the<SPAN>&nbsp;</SPAN><STRONG>Pipelines</STRONG><SPAN>&nbsp;</SPAN>section of your Azure DevOps project and create a new pipeline. As usual, first you will have to choose the repository where your code is hosted, followed by the starting template. In my case it's a Windows Forms application, so I chose the<SPAN>&nbsp;</SPAN><STRONG>.NET Desktop</STRONG><SPAN>&nbsp;</SPAN>one, which looks like this:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-77" class="language-yaml hljs"><SPAN class="hljs-comment"># .NET Desktop</SPAN> <SPAN class="hljs-comment"># Build and run tests for .NET Desktop or Windows classic desktop solutions.</SPAN> <SPAN class="hljs-comment"># Add steps that publish symbols, save build artifacts, and more:</SPAN> <SPAN class="hljs-comment"># https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net</SPAN> <SPAN class="hljs-attr">trigger:</SPAN> <SPAN class="hljs-bullet">-</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-attr">pool:</SPAN> <SPAN class="hljs-attr"> vmImage:</SPAN> <SPAN class="hljs-string">'windows-latest'</SPAN> <SPAN class="hljs-attr">variables:</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'**/*.sln'</SPAN> <SPAN class="hljs-attr"> buildPlatform:</SPAN> <SPAN class="hljs-string">'Any CPU'</SPAN> <SPAN class="hljs-attr"> buildConfiguration:</SPAN> <SPAN class="hljs-string">'Release'</SPAN> <SPAN class="hljs-attr">steps:</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">NuGetToolInstaller@1</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">NuGetCommand@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> restoreSolution:</SPAN> <SPAN class="hljs-string">'$(solution)'</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">VSBuild@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'$(solution)'</SPAN> <SPAN class="hljs-attr"> platform:</SPAN> <SPAN class="hljs-string">'$(buildPlatform)'</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">'$(buildConfiguration)'</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">VSTest@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> platform:</SPAN> <SPAN class="hljs-string">'$(buildPlatform)'</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">'$(buildConfiguration)'</SPAN> </CODE></PRE> <P>It's a good starting point, since it already takes care of restoring the NuGet packages and building the code. The next step is to add the Advanced Installer tasks, so that the code can be converted into an installer. We're going to add two tasks: one to generate a MSI and one to generate a MSIX.</P> <P>Let's see the first one:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-117" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">AdvancedInstaller@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> advinstLicense:</SPAN> <SPAN class="hljs-string">'$(AILicense)'</SPAN> <SPAN class="hljs-attr"> aipPath:</SPAN> <SPAN class="hljs-string">'Projects\MyEmployees.aip'</SPAN> <SPAN class="hljs-attr"> aipBuild:</SPAN> <SPAN class="hljs-string">'DefaultBuild'</SPAN> <SPAN class="hljs-attr"> aipPackageName:</SPAN> <SPAN class="hljs-string">'MyEmployees-$(Build.BuildNumber).msi'</SPAN> <SPAN class="hljs-attr"> aipOutputFolder:</SPAN> <SPAN class="hljs-string">'$(Build.ArtifactStagingDirectory)\MSI'</SPAN> </CODE></PRE> <P>The configuration is quite simple:</P> <P>&nbsp;</P> <UL id="pragma-line-129"> <LI id="pragma-line-129"><CODE>aiPath</CODE><SPAN>&nbsp;</SPAN>is the path, starting from the root of the repository, which contains the Advanced Installer project.</LI> <LI id="pragma-line-130"><CODE>aipBuild</CODE><SPAN>&nbsp;</SPAN>is the name of the build definition we want to run. In this case we're creating the MSI first, so we use the<SPAN>&nbsp;</SPAN><CODE>DefaultBuild</CODE><SPAN>&nbsp;</SPAN>definition.</LI> <LI id="pragma-line-131">The<SPAN>&nbsp;</SPAN><CODE>aipPackageName</CODE><SPAN>&nbsp;</SPAN>parameter is optional. However, in my case, I prefer to add a reference to the build number in the file name generated by the tool, so that it's easier for me to understand the version number. As such, I have added the variable<SPAN>&nbsp;</SPAN><CODE>$(Build.BuildNumber)</CODE><SPAN>&nbsp;</SPAN>as suffix to the file name.</LI> <LI id="pragma-line-132"><CODE>aipOutputFolder</CODE><SPAN>&nbsp;</SPAN>specifies where you want to copy the generated packages. In this case I use a folder called MSI, which is created under the default folder where artifacts are picked up. This way, it will be easier for me to publish the generated MSI as build artifact.</LI> </UL> <P>The<SPAN>&nbsp;</SPAN><CODE>advinstLicense</CODE><SPAN>&nbsp;</SPAN>parameter deserves a special mention. As anticipated in the beginning of the post, Advanced Installer is a paid product. As such, if you're building a project's type which isn't covered by the free license, you will need to provide your license key. Since YAML files are typically included in the repository, it isn't a good idea to store it in clear. As such, I have used the<SPAN>&nbsp;</SPAN><STRONG>Variables</STRONG><SPAN>&nbsp;</SPAN>panel to create a variable called<SPAN>&nbsp;</SPAN><STRONG>AILicense</STRONG><SPAN>&nbsp;</SPAN>with the key and then I've referenced it in the YAML using the<SPAN>&nbsp;</SPAN><CODE>$(AILicense)</CODE><SPAN>&nbsp;</SPAN>keyword.</P> <P>&nbsp;</P> <P>Now that you have seen how to configure the task, it will be very easy to add a new one to generate the MSIX package:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-138" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">AdvancedInstaller@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> advinstLicense:</SPAN> <SPAN class="hljs-string">'$(AILicense)'</SPAN> <SPAN class="hljs-attr"> aipPath:</SPAN> <SPAN class="hljs-string">'Projects\MyEmployees.aip'</SPAN> <SPAN class="hljs-attr"> aipBuild:</SPAN> <SPAN class="hljs-string">'Build_MSIX_APPX'</SPAN> <SPAN class="hljs-attr"> aipPackageName:</SPAN> <SPAN class="hljs-string">'MyEmployees-$(Build.BuildNumber).msix'</SPAN> <SPAN class="hljs-attr"> aipOutputFolder:</SPAN> <SPAN class="hljs-string">'$(Build.ArtifactStagingDirectory)\MSIX'</SPAN> </CODE></PRE> <P>We change just the name of the build definition to run, the name of the package (this time the extension will be .msix and not .msi) and the output folder. To keep things clean, in fact, I create the MSIX package in a different folder, called MSIX.</P> <P>Now the last step is to publish the build artifacts, by adding a publish task:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-152" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">PublishBuildArtifacts@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> PathtoPublish:</SPAN> <SPAN class="hljs-string">'$(Build.ArtifactStagingDirectory)'</SPAN> <SPAN class="hljs-attr"> ArtifactName:</SPAN> <SPAN class="hljs-string">'drop'</SPAN> <SPAN class="hljs-attr"> publishLocation:</SPAN> <SPAN class="hljs-string">'Container'</SPAN> </CODE></PRE> <P>That's it! Now save the pipeline, which will automatically trigger a new build. Azure DevOps will first compile the Windows Forms application and then it will execute the Advanced Installer task twice, one for the MSI and one for the MSIX. The first task will take longer, because it will take care also of downloading the tool on the hosted agent. The second task, instead, will reuse the same one.</P> <P>At the end of the process, if you explore the artifacts you should see something like this:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Artifacts.png" style="width: 996px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168310i4E810D1282811CF5/image-size/large?v=v2&amp;px=999" role="button" title="Artifacts.png" alt="Artifacts.png" /></span></P> <P>&nbsp;</P> <H3 id="create-a-release-pipeline">Create a release pipeline</H3> <P>Now that you have created a CI pipeline, it's time to create a CD pipeline to automate the deployment of these packages. Move to the<SPAN>&nbsp;</SPAN><STRONG>Releases</STRONG><SPAN>&nbsp;</SPAN>section on Azure DevOps and press the button to create a new pipeline. You will be prompted with a series of templates. We're going to start from an empty job, so click on the related link.</P> <P>&nbsp;</P> <P><SPAN>Let's start from the one to deploy the MSI, so let's call it&nbsp;</SPAN><STRONG>Deploy MSI</STRONG><SPAN>. But first, click on&nbsp;</SPAN><STRONG>Add an artifact</STRONG><SPAN>&nbsp;in the&nbsp;</SPAN><STRONG>Artifacts</STRONG><SPAN>&nbsp;section and choose the one produced by the CI pipeline you have just created.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ArtifactsConfiguration.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168311i643FA1DC327A41EF/image-size/large?v=v2&amp;px=999" role="button" title="ArtifactsConfiguration.png" alt="ArtifactsConfiguration.png" /></span></SPAN></P> <P>&nbsp;</P> <P>Now you can click on the link under the task name to start adding tasks. The first step is to sign the installer, to avoid the warnings by Windows that are displayed when you tried to install an untrusted installer. The easiest way to achieve this goal is to use an extension created by Stefan Kert called Code Signing. If you haven't already installed it on your Azure DevOps account,<SPAN>&nbsp;</SPAN><A href="#" target="_blank">go on and install it</A>.</P> <P>&nbsp;</P> <P>Then click on the + symbol near<SPAN>&nbsp;</SPAN><STRONG>Agent job</STRONG>, look for the<SPAN>&nbsp;</SPAN><STRONG>Code Signing</STRONG><SPAN>&nbsp;</SPAN>task and add it. You'll need to have a .pfx file with the certificate that you're going to use to sign the installer. You can acquire one from a public certificate authority or get it from your internal one if the installer is meant for enterprise deployment; alternatively, if you're just doing some tests, you can generate<SPAN>&nbsp;</SPAN><A href="#" target="_blank">a self-signed certificate</A>.</P> <P>Here are the field to configure:</P> <P>&nbsp;</P> <UL id="pragma-line-182"> <LI id="pragma-line-182"><STRONG>Secure File</STRONG><SPAN>&nbsp;</SPAN>is the path to the file which contains your certificate. The task uses the<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Secure Files</A><SPAN>&nbsp;</SPAN>feature from Azure DevOps, which means that we can safely upload it by clicking on the Settings button near the field. The file will be available only to be used in this pipeline; no one will be able to download it and reuse it, blocking malicious actors that could be interested in stealing our identity.</LI> <LI id="pragma-line-183"><STRONG>Secure File Password</STRONG><SPAN>&nbsp;</SPAN>is the password you have used to protect your certificate. Since it's better to not store the password in clear, I have setup a variable called<SPAN>&nbsp;</SPAN><CODE>$(PfxPassword)</CODE><SPAN>&nbsp;</SPAN>in the<SPAN>&nbsp;</SPAN><STRONG>Variables</STRONG><SPAN>&nbsp;</SPAN>tab.</LI> <LI id="pragma-line-184"><STRONG>File(s) to sign</STRONG><SPAN>&nbsp;</SPAN>is the path of the file to sign. In this case, we just set it to<SPAN>&nbsp;</SPAN><CODE>**/*.msi</CODE>. We generically sign every file with .msi extension in the artifacts folder.</LI> </UL> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="CodeSigning.png" style="width: 460px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168312i5402CC2182AB1C1B/image-size/large?v=v2&amp;px=999" role="button" title="CodeSigning.png" alt="CodeSigning.png" /></span></P> <P>&nbsp;</P> <P>That's it. The next step is... well, it's up to you =)</img> Now that the installer has been signed, you can add a task to deploy it in the place which makes more sense for you. For example, you can deploy it to a storage or a FTP, where the file will be linked by a web page. In my scenario, I'm using Azure Storage to store the file in a blob, so I'm using the task called<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Azure File Copy</A>.</P> <P>&nbsp;</P> <P>Once you've completed the task, you can click on<SPAN>&nbsp;</SPAN><STRONG>Pipeline</STRONG><SPAN>&nbsp;</SPAN>and go back to setup the MSIX package deployment. In the<SPAN>&nbsp;</SPAN><STRONG>Stages</STRONG><SPAN>&nbsp;</SPAN>section click on<SPAN>&nbsp;</SPAN><STRONG>Add</STRONG><SPAN>&nbsp;</SPAN>and choose<SPAN>&nbsp;</SPAN><STRONG>New stage</STRONG>. Also in this case choose to create an empty job and give it a meaningful name, like<SPAN>&nbsp;</SPAN><STRONG>Deploy MSIX</STRONG>.</P> <P>&nbsp;</P> <P>This is how your pipeline should look like:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ReleasePipeline.png" style="width: 675px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168313i9B95B0355DC3B45C/image-size/large?v=v2&amp;px=999" role="button" title="ReleasePipeline.png" alt="ReleasePipeline.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Click on the link under the new stage to start adding tasks. Also in this case the required one is the&nbsp;</SPAN><STRONG>Code Signing</STRONG><SPAN>&nbsp;tasks, which you need to sign the MSIX package. The configuration is exactly the same as we did for the MSI, as long as you're using the same certificate. The only difference is that the&nbsp;</SPAN><STRONG>File(s) to sign</STRONG><SPAN>&nbsp;field must be changed to&nbsp;</SPAN><CODE>**/*.msix</CODE><SPAN>, as in the following image:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="CodeSigningMSIX.png" style="width: 466px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168315i39EB9E7DCD169160/image-size/large?v=v2&amp;px=999" role="button" title="CodeSigningMSIX.png" alt="CodeSigningMSIX.png" /></span></SPAN></P> <P>&nbsp;</P> <P><SPAN>Pay attention that, in order for the signing to complete successful, the subject of the certificate must match the publisher declared in the manifest. You can check this in Advanced Installer. Move to the&nbsp;<STRONG>Package Information</STRONG>&nbsp;tab under the&nbsp;<STRONG>Universal Windows</STRONG>&nbsp;section and look for&nbsp;<STRONG>Publisher</STRONG>&nbsp;section:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Publisher.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168316i080774FDD2EB1FAA/image-size/large?v=v2&amp;px=999" role="button" title="Publisher.png" alt="Publisher.png" /></span></SPAN></P> <P>The value that must match the subject of your certificate is the one in the<SPAN>&nbsp;</SPAN><STRONG>ID</STRONG><SPAN>&nbsp;</SPAN>field.</P> <P>&nbsp;</P> <P>Now you're ready for the deployment. Also in this case you can choose the task which fits best your scenario. For example, you can deploy the MSIX package to Azure Storage and leverage the App Installer technology to handle the installation and automatic updates. If you want to generate an App Installer file as part of the build process, you can leverage again Advanced Installer. Move to the<SPAN>&nbsp;</SPAN><STRONG>Builds</STRONG><SPAN>&nbsp;</SPAN>section of your project and, in the MSIX build definition, look for the section titled<SPAN>&nbsp;</SPAN><STRONG>AppInstaller</STRONG>. There you will be able to provide all the relevant information, like the URL where you're going to deploy the package, the frequency check for automatic updates, etc.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AppInstaller.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168317iF94B6707A944C502/image-size/large?v=v2&amp;px=999" role="button" title="AppInstaller.png" alt="AppInstaller.png" /></span></P> <P>&nbsp;</P> <P>Remember that some options might be disabled, based on the Windows 10 version you're targeting in the<SPAN>&nbsp;</SPAN><STRONG>Target Platforms</STRONG><SPAN>&nbsp;</SPAN>section. For example, the<SPAN>&nbsp;</SPAN><STRONG>Show Prompt</STRONG><SPAN>&nbsp;</SPAN>option is supported starting from Windows 10 1903, so if you're targeting a lower version the option will be disabled.</P> <P>&nbsp;</P> <P>Remember to commit the updated .ai file to your repository every time you make any change to the Advanced Installer project. This way, you will trigger a new build and a new generation of the MSI and MSIX installers. As last step, if you truly want to enable a full CI/CD experience, remember to click, in the Release pipeline, on the lighting symbol in the<SPAN>&nbsp;</SPAN><STRONG>Artifacts</STRONG><SPAN>&nbsp;</SPAN>section and set to<SPAN>&nbsp;</SPAN><STRONG>Enabled</STRONG><SPAN>&nbsp;</SPAN>the Continuous Deployment Trigger.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ContinuousDeployment.png" style="width: 995px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168318iB8A3073D083501C0/image-size/large?v=v2&amp;px=999" role="button" title="ContinuousDeployment.png" alt="ContinuousDeployment.png" /></span></P> <P>&nbsp;</P> <P><SPAN>This way, Azure DevOps will automatically trigger the Release pipeline every time the build pipeline completes successfully.</SPAN></P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>In this blog post we have seen how, with Advanced Installer, we can enable a CI/CD pipeline on Azure DevOps that automatically generates both a MSI and a MSIX package out from the code of our application. This way, we can keep supporting customers who aren't using Windows 10 yet but, at the same time, provide a much better deployment experience for the ones who already doing it. Hopefully, over time, you will be able to stop creating a MSI and focus only on MSIX =)</img></P> <P>There are many great 3rd party tools on the market that can help you in the process of creating installers and packages for your applications. Advanced Installer is one of them and one thing I have appreciated is the availability of an Azure DevOps task, which allows me to use one of the built-in hosted agents, without forcing me to maintain a dedicated agent with the required tools.</P> <P>&nbsp;</P> <P>You can find the sample used in this project, together with the full pipeline definition, on<SPAN>&nbsp;</SPAN><A href="#" target="_blank">GitHub</A>.</P> <P>&nbsp;</P> <P>Happy packaging!</P> <P>&nbsp;</P> Fri, 31 Jan 2020 14:43:55 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-installers-for-your-windows-applications-with-advanced/ba-p/1142985 Matteo Pagani 2020-01-31T14:43:55Z Usuful documents to migrate WPF/WinForms apps from .NET Framework to .NET Core https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/usuful-documents-to-migrate-wpf-winforms-apps-from-net-framework/ba-p/1142017 <DIV>Breaking change selectors:</DIV> <DIV><A title="Original URL: https://docs.microsoft.com/en-us/dotnet/core/compatibility/breaking-changes. Click or tap if you trust this link." href="#" target="_blank" rel="noopener noreferrer" data-auth="Verified">https://docs.microsoft.com/en-us/dotnet/core/compatibility/breaking-changes</A></DIV> <DIV>&nbsp;</DIV> <DIV>You can select between two different versions such as from .NET Framework to .NET Core:</DIV> <DIV><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_0.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168235i89BBAA7A83B0412E/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_0.png" alt="clipboard_image_0.png" /></span></DIV> <DIV>&nbsp;</DIV> <DIV>And you can also select area(But there is not WPF, WinForms is there):</DIV> <DIV><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168236iF228DE83E491E3D7/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_1.png" alt="clipboard_image_1.png" /></span></DIV> <DIV>&nbsp;</DIV> <DIV>After selected, you can see list of breaking changes:</DIV> <DIV><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_2.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/168237i26791EBFD41834F1/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_2.png" alt="clipboard_image_2.png" /></span></DIV> <DIV>&nbsp;</DIV> <DIV>Other useful sites:</DIV> <UL> <LI>Migrating WPF apps to .NET Core (.NET Portability Analyzer, CsprojToVs2017, etc...)<BR /><A href="#" target="_blank">https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework</A></LI> <LI>Windows Apps modernization workshop<BR /><A href="#" target="_blank">https://github.com/microsoft/AppConsult-WinAppsModernizationWorkshop</A></LI> <LI>.NET API Analyzer<BR /><A href="#" target="_blank">https://docs.microsoft.com/en-us/dotnet/standard/analyzers/api-analyzer</A></LI> </UL> <P>I hope the documents help you.</P> <P>Thanks!</P> Fri, 31 Jan 2020 02:09:34 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/usuful-documents-to-migrate-wpf-winforms-apps-from-net-framework/ba-p/1142017 KazukiOta 2020-01-31T02:09:34Z MSIX: how to copy data outside the installation folder during the deployment https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/msix-how-to-copy-data-outside-the-installation-folder-during-the/ba-p/1133602 <P>When you deploy a MSIX package on Windows 10, all the files are copied in a special folder called<SPAN>&nbsp;</SPAN><CODE>C:\Program Files\WindowsApps</CODE>. Each application has its own folder, which name is represented by the<SPAN>&nbsp;</SPAN><STRONG>Package Full Name</STRONG>, which is composed by:</P> <P>&nbsp;</P> <UL> <LI>The Package Family Name (which is composed by Publisher and Name)</LI> <LI>The version number</LI> <LI>The target CPU architecture</LI> </UL> <P>For example, if you download the MSIX Packaging Tool from the Microsoft Store, it will be deployed in the folder</P> <P>&nbsp;</P> <PRE><CODE class="language-plaintext hljs">C:\Program Files\WindowsApps\Microsoft.MsixPackagingTool_1.2019.1220.0_x64__8wekyb3d8bbwe </CODE></PRE> <P>In order to track all the files which belong to the application more efficiently, Windows doesn't allow to deploy files outside this folder. However, you might have a scenario where you actually need to do this. For example, you must copy a dependency in a system folder; or your application comes with a set of data or configuration files you need to deploy in the local AppData folder of the user.</P> <P>&nbsp;</P> <P>To overcome this requirement, MSIX provides a feature called<SPAN>&nbsp;</SPAN><STRONG>Virtual File System</STRONG>, which gives you the possibility to map the most common system folders (like<SPAN>&nbsp;</SPAN><CODE>C:\Windows</CODE><SPAN>&nbsp;</SPAN>or<SPAN>&nbsp;</SPAN><CODE>C:\Program Files</CODE>) inside a special folder inside the package called<SPAN>&nbsp;</SPAN><STRONG>VFS</STRONG>. When the application looks for a file inside these folders, Windows will redirect the call to the VFS folder and will try to see if the file is included in the Virtual File System. This feature provides two main benefits:</P> <P>&nbsp;</P> <UL> <LI>You get the opportunity to create truly standalone MSIX packages. Even if your application has one or more dependencies, you can bundle everything together, without asking to the user to manually install a framework or a library your application depends on.</LI> <LI>It helps to minimize the problem known as DLL Hell, which happens when you have multiple applications depending on different version of the same framework. Typically this framework can be installed only system-wide, so you can't keep multiple versions of the same one on the machine. This means that, if the most recent version contains some breaking changes, your applications which depends on older versions might brake. With the Virtual File System you can include, inside the package, the specific version of the dependency you need and it won't interfere with the already installed ones.</LI> </UL> <P>The Virtual File System is a great way to overcome this MSIX limitation when it comes to deploy a dependency. But what about when you need to copy data or configuration files in the local AppData folder or a library like Documents? In this case, the Virtual File System can't really help since, as<SPAN>&nbsp;</SPAN><A href="#" target="_blank">per the documentation</A>, it supports mostly system folders, not user's folder (except the generic C:\ProgramData one).</P> <P>&nbsp;</P> <P>Let's see which options we have.</P> <P>&nbsp;</P> <H3 id="the-sample-app">The sample app</H3> <P>As a starting point, we're going to use a simple Windows Forms application, which retrieves a list of employees from a local SQLite database. The whole code is available<SPAN>&nbsp;</SPAN><A href="#" target="_blank">here</A>. The connection with the database is handled using the<SPAN>&nbsp;</SPAN><A href="#" target="_blank">System.Data.SQLite</A><SPAN>&nbsp;</SPAN>library, which is an ADO.NET provider for SQLite, the popular self-contained database. When the application starts, it uses a class called<SPAN>&nbsp;</SPAN><CODE>SQLiteConnection</CODE><SPAN>&nbsp;</SPAN>to establish a connection with a local SQLIte file. Then, using a<SPAN>&nbsp;</SPAN><CODE>SQLiteCommand</CODE><SPAN>&nbsp;</SPAN>object, it performs a SELECT query on the database to retrieve the list of employees. In the end, using a<SPAN>&nbsp;</SPAN><CODE>SQLDataReader</CODE><SPAN>&nbsp;</SPAN>object, it iterates through all the resulting rows and it adds them to a<SPAN>&nbsp;</SPAN><CODE>BindingSource</CODE><SPAN>&nbsp;</SPAN>object, which is connected to a<SPAN>&nbsp;</SPAN><CODE>DataGrid</CODE><SPAN>&nbsp;</SPAN>control in the UI.</P> <P>&nbsp;</P> <P>This is the complete code:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">LoadData</SPAN>()</SPAN> { <SPAN class="hljs-keyword">string</SPAN> dbPath = <SPAN class="hljs-string">$"<SPAN class="hljs-subst">{Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)}</SPAN>\\MyEmployees\\Employees.db"</SPAN>; <SPAN class="hljs-keyword">if</SPAN> (File.Exists(dbPath)) { SQLiteConnection connection = <SPAN class="hljs-keyword">new</SPAN> SQLiteConnection(<SPAN class="hljs-string">$"Data Source= <SPAN class="hljs-subst">{dbPath}</SPAN>"</SPAN>); <SPAN class="hljs-keyword">using</SPAN> (SQLiteCommand command = <SPAN class="hljs-keyword">new</SPAN> SQLiteCommand(connection)) { connection.Open(); command.CommandText = <SPAN class="hljs-string">"SELECT * FROM Employees"</SPAN>; <SPAN class="hljs-keyword">using</SPAN> (SQLiteDataReader reader = command.ExecuteReader()) { <SPAN class="hljs-keyword">while</SPAN> (reader.Read()) { Employee employee = <SPAN class="hljs-keyword">new</SPAN> Employee { EmployeeId = <SPAN class="hljs-keyword">int</SPAN>.Parse(reader[<SPAN class="hljs-number">0</SPAN>].ToString()), FirstName = reader[<SPAN class="hljs-number">1</SPAN>].ToString(), LastName = reader[<SPAN class="hljs-number">2</SPAN>].ToString(), Email = reader[<SPAN class="hljs-number">3</SPAN>].ToString() }; employeeBindingSource.Add(employee); } } } dataGridView1.DataSource = employeeBindingSource; } } </CODE></PRE> <P>As you can see from the first line of the method, the application is expecting to find the SQLite database in the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>folder. The<SPAN>&nbsp;</SPAN><CODE>Environment.SpecialFolder.CommonApplicationData</CODE><SPAN>&nbsp;</SPAN>folder is translated with the<SPAN>&nbsp;</SPAN><CODE>%PROGRAMDATA%</CODE><SPAN>&nbsp;</SPAN>environment variable. This means that the application looks for the database in the path<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData\MyEmployees\Employees.db</CODE>. The package of the application includes a file called<SPAN>&nbsp;</SPAN><CODE>Employees.db</CODE>, which we want to copy in this folder. Without doing this, the app would display an empty DataGrid when it starts. However, this is a task that we can't achieve directly with MSIX, since the content of the package (including our<SPAN>&nbsp;</SPAN><CODE>Employees.db</CODE><SPAN>&nbsp;</SPAN>file) will be copied only in the special folder under<SPAN>&nbsp;</SPAN><CODE>C:\Program Files\WindowsApps</CODE>. We could use the Virtual File System, since<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>is one of the mapped folders, but we want our database to be modifiable. If we leave it in the package folder, it would be read-only instead.</P> <P>&nbsp;</P> <H3 id="option-1-copy-the-files-when-the-application-starts">Option 1: copy the files when the application starts</H3> <P>This might seem obvious, but it's the best option if we're talking about an application which is still actively developed. When the application starts for the first time, we can just copy the file into the local<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>folder. We can use multiple ways to detect if the it's the first launch: we can add a key in the registry; we can store the information in a configuration file; or we can simply look if the database already exists in the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>folder.</P> <P>Here is an example of a method that we can add to our application:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">CopyDatabase</SPAN>()</SPAN> { <SPAN class="hljs-keyword">string</SPAN> result = Assembly.GetExecutingAssembly().Location; <SPAN class="hljs-keyword">int</SPAN> index = result.LastIndexOf(<SPAN class="hljs-string">"\\"</SPAN>); <SPAN class="hljs-keyword">string</SPAN> dbPath = <SPAN class="hljs-string">$"<SPAN class="hljs-subst">{result.Substring(<SPAN class="hljs-number">0</SPAN>, index)}</SPAN>\\Employees.db"</SPAN>; <SPAN class="hljs-keyword">string</SPAN> destinationPath = <SPAN class="hljs-string">$"<SPAN class="hljs-subst">{Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)}</SPAN>\\MyEmployees\\Employees.db"</SPAN>; <SPAN class="hljs-keyword">string</SPAN> destinationFolder = <SPAN class="hljs-string">$"<SPAN class="hljs-subst">{Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)}</SPAN>\\MyEmployees\\"</SPAN>; <SPAN class="hljs-keyword">if</SPAN> (!File.Exists(destinationPath)) { Directory.CreateDirectory(destinationFolder); File.Copy(dbPath, destinationPath, <SPAN class="hljs-literal">true</SPAN>); } } </CODE></PRE> <P>First we get a reference to the<SPAN>&nbsp;</SPAN><CODE>Employees.db</CODE><SPAN>&nbsp;</SPAN>file which is included in the package folder. We use reflection since, when an application runs as packaged, the Current Working Directory is mapped with the<SPAN>&nbsp;</SPAN><CODE>C:\Windows\System32</CODE><SPAN>&nbsp;</SPAN>or<SPAN>&nbsp;</SPAN><CODE>C:\Windows\SysWOW64</CODE><SPAN>&nbsp;</SPAN>folder. As such, we can't simply use the<SPAN>&nbsp;</SPAN><CODE>Directory.GetCurrentDirectory()</CODE><SPAN>&nbsp;</SPAN>API to achieve this task. Once we have the path of the database file, we use the<SPAN>&nbsp;</SPAN><CODE>Environment.SpecialFolder.CommonApplicationData</CODE><SPAN>&nbsp;</SPAN>property to compose the full path of the destination folder. In this case, we want to copy the database inside the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData\MyEmployees</CODE><SPAN>&nbsp;</SPAN>folder.</P> <P>&nbsp;</P> <P>In the end, if the<SPAN>&nbsp;</SPAN><CODE>Employees.db</CODE><SPAN>&nbsp;</SPAN>file doesn't exist in the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>folder, it means that it's the first execution of the app. As such, we go on and we copy the database from the package inside the destination folder.</P> <P>&nbsp;</P> <P>That's it. Now we just need to make sure that the<SPAN>&nbsp;</SPAN><CODE>CopyDatabase()</CODE><SPAN>&nbsp;</SPAN>method we have just defined is invoked before the<SPAN>&nbsp;</SPAN><CODE>LoadData()</CODE><SPAN>&nbsp;</SPAN>one we have previously seen. This is how the event handler of the<SPAN>&nbsp;</SPAN><CODE>Load</CODE><SPAN>&nbsp;</SPAN>event of the form will look like:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">Form1_Load</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, EventArgs e</SPAN>)</SPAN> { CopyDatabase(); LoadData(); } </CODE></PRE> <P>Now we can package our application with MSIX by adding a<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Windows Application Packaging Project</A><SPAN>&nbsp;</SPAN>to our solution. If we did everything properly, when we launch the application we will see the list of the employees in the DataGrid. And if we use File Explorer to open the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData\MyEmployees</CODE><SPAN>&nbsp;</SPAN>path, we will see the<SPAN>&nbsp;</SPAN><CODE>Employees.db</CODE><SPAN>&nbsp;</SPAN>file we have copied.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="DatabaseFile.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167728iB34AD67F708225E3/image-size/large?v=v2&amp;px=999" role="button" title="DatabaseFile.png" alt="DatabaseFile.png" /></span></P> <P>&nbsp;</P> <H3 id="option-2-copy-the-files-with-a-powershell-script">Option 2: copy the files with a PowerShell script</H3> <P>In some cases, we might not be able to change the code of our application. It might be because you aren't a developer, but you manage the deployment of applications in your enterprise and, as such, you don't have the source code; or it's a legacy app built with a very old technology and it would be risky to make any change. In this scenario, we can leverage a feature that has been recently added to the<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Package Support Framework</A>: scripting support. We have already talked about this framework<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/solving-common-desktop-bridge-blockers-with-the-package-support/ba-p/317457" target="_blank">on this blog</A>: it's an open source framework which provides support to fixups, which are DLLs that are able to change the behavior of your application at runtime. This way, you can solve common MSIX blockers (like apps which write to the installation folder or need to access to the current working directory) without changing the code.</P> <P>&nbsp;</P> <P>Since a few months, the Package Support Framework<SPAN>&nbsp;</SPAN><A href="#" target="_blank">has added supports to script</A>. You can include inside your package one or more PowerShell scripts and execute them on various conditions: when the application starts or closes, only the first time after the MSIX package has been deployed or every time, etc.</P> <P>&nbsp;</P> <P>This feature is a great fit for our scenario: we can include inside the package a script that takes care of copying the database file to the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>folder, without having to change the code. Let's see how to do it. We're going to use Visual Studio and the Windows Application Packaging Project since I'm a developer, but you can perform also the following steps manually, by unpacking the MSIX package, injecting the Package Support Framework and then repackaging it again. This way, you can leverage the same approach even if you aren't a developer or you don't use Visual Studio. This approach is described<SPAN>&nbsp;</SPAN><A href="#" target="_blank">in the official documentation</A>.</P> <P>&nbsp;</P> <H4 id="step-1-create-the-powershell-script">Step 1: create the PowerShell script</H4> <P>As first step, let's create the PowerShell script which is going to copy the database from the package to the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>folder. You can use any text editor, from Notepad to Visual Studio code. This is the script to copy and paste:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs"><SPAN class="hljs-built_in">New-Item</SPAN> -Path <SPAN class="hljs-variable">$env:ProgramData</SPAN> -Name <SPAN class="hljs-string">"MyEmployees"</SPAN> -ItemType <SPAN class="hljs-string">"directory"</SPAN> <SPAN class="hljs-built_in">Copy-Item</SPAN> -Path <SPAN class="hljs-string">"Employees.db"</SPAN> -Destination <SPAN class="hljs-string">"<SPAN class="hljs-variable">$env:ProgramData</SPAN>\MyEmployees"</SPAN> -Recurse </CODE></PRE> <P>First we use the<SPAN>&nbsp;</SPAN><CODE>New-Item</CODE><SPAN>&nbsp;</SPAN>command to create a new folder called<SPAN>&nbsp;</SPAN><STRONG>MyEmployees</STRONG><SPAN>&nbsp;</SPAN>in the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData</CODE><SPAN>&nbsp;</SPAN>path. Then we use the<SPAN>&nbsp;</SPAN><CODE>Copy-Item</CODE><SPAN>&nbsp;</SPAN>command to copy the file<SPAN>&nbsp;</SPAN><STRONG>Employees.db</STRONG><SPAN>&nbsp;</SPAN>to the folder we have just created.</P> <P>&nbsp;</P> <H4 id="step-2-add-the-package-support-framework">Step 2: add the Package Support Framework</H4> <P>First we need to download the Package Support Framework, which is distributed through NuGet. However, it isn't a traditional library, so we won't have to add it to a project in Visual Studio, but we're going to download it and copy some files inside our Visual Studio solution. The easiest way to achieve this task is by using the NuGet CLI, which can be downloaded from<SPAN>&nbsp;</SPAN><A href="#" target="_blank">the official website</A>. Download the most recent Windows x86 command line version:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NuGet.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167729i582D26A62379B183/image-size/large?v=v2&amp;px=999" role="button" title="NuGet.png" alt="NuGet.png" /></span></P> <P>&nbsp;</P> <P>Once you have downloaded and copied it somewhere on your computer, open a command prompt on that location and run the following command:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">nuget install Microsoft.PackageSupportFramework </CODE></PRE> <P>At the end of the process, you will find in the same location a folder called<SPAN>&nbsp;</SPAN><STRONG>Microsoft.PackageSupportFramework</STRONG><SPAN>&nbsp;</SPAN>followed by the version number of the most recent version. For example, at the moment of writing this post, the folder is called<SPAN>&nbsp;</SPAN><STRONG>Microsoft.PackageSupportFramework.1.0.200102.1</STRONG>. Go inside it and move to the<SPAN>&nbsp;</SPAN><STRONG>bin</STRONG><SPAN>&nbsp;</SPAN>folder. We're going to copy a few files inside the Windows Application Packaging Project, by dragging them from File Explorer to the WAP project in Visual Studio. If your application is compiled for x86, copy the<SPAN>&nbsp;</SPAN><STRONG>PsfLauncher32.exe</STRONG>,<SPAN>&nbsp;</SPAN><STRONG>PsfRunDll32.exe</STRONG><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><STRONG>PsfRuntime32.dll</STRONG><SPAN>&nbsp;</SPAN>files. If instead, it's compiled for x64, copy the<SPAN>&nbsp;</SPAN><STRONG>PsfLauncher64.exe</STRONG>,<SPAN>&nbsp;</SPAN><STRONG>PsfRunDll64.exe</STRONG><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><STRONG>PsfRuntime64.dll</STRONG><SPAN>&nbsp;</SPAN>files. My sample on GitHub is compiled for 64 bits, so I've added the files with 64 suffix.</P> <P>The next step is to include inside the package a PowerShell script, which is used by the Package Support Framework to invoke the other custom scripts you include. This file is called<SPAN>&nbsp;</SPAN><STRONG>StartingScriptWrapper.ps1</STRONG><SPAN>&nbsp;</SPAN>and it's included in the<SPAN>&nbsp;</SPAN><STRONG>bin</STRONG><SPAN>&nbsp;</SPAN>folder as well. However, unlike for the previous files, it doesn't have to be included in the root of the package, but in the same folder which contains the main executable of the application. In some cases the two things might coincide, but you can't take it for granted. For example, in case you're using the Windows Application Packaging Project, Visual Studio will put the main application's executable and the DLLs inside a sub-folder of the package, with the same name of the application. For instance, my sample app is called<SPAN>&nbsp;</SPAN><STRONG>MyEmployees</STRONG>, so the main executable and files of the application will be stored in a folder called<SPAN>&nbsp;</SPAN><STRONG>MyEmployees</STRONG><SPAN>&nbsp;</SPAN>inside the package. This is where we need to copy the<SPAN>&nbsp;</SPAN><STRONG>StartingScriptWrapper.ps1</STRONG><SPAN>&nbsp;</SPAN>file. To achieve this goal, it's enough to manually add a folder called<SPAN>&nbsp;</SPAN><STRONG>MyEmployees</STRONG><SPAN>&nbsp;</SPAN>inside the Windows Application Packaging Project. It will be merged with the one generated by Visual Studio.</P> <P>&nbsp;</P> <P>Inside this folder we have to include also the PowerShell script that we have created at Step 1. This is how the final project should look like:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ProjectFile.png" style="width: 313px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167730i158E3D15034C3D94/image-size/large?v=v2&amp;px=999" role="button" title="ProjectFile.png" alt="ProjectFile.png" /></span></P> <P>&nbsp;</P> <H4 id="step-3-configure-the-package-support-framework">Step 3: configure the Package Support Framework</H4> <P>The last step is to configure the Package Support Framework. So far we have just injected the required files, but we didn't set it up. If we would create a MSIX package now out from the Windows Application Packaging Project, we won't notice any difference. The main application would simply be launched, as before.</P> <P>&nbsp;</P> <P>To configure the framework we need to include a file inside the Windows Application Packaging Project called<SPAN>&nbsp;</SPAN><STRONG>config.json</STRONG>. Then copy and paste the following content:</P> <P>&nbsp;</P> <PRE><CODE class="language-json hljs">{ <SPAN class="hljs-attr">"applications"</SPAN>: [ { <SPAN class="hljs-attr">"id"</SPAN>: <SPAN class="hljs-string">"App"</SPAN>, <SPAN class="hljs-attr">"executable"</SPAN>: <SPAN class="hljs-string">"MyEmployees/MyEmployees.exe"</SPAN>, <SPAN class="hljs-attr">"workingDirectory"</SPAN>: <SPAN class="hljs-string">"MyEmployees"</SPAN>, <SPAN class="hljs-attr">"stopOnScriptError"</SPAN>: <SPAN class="hljs-literal">true</SPAN>, <SPAN class="hljs-attr">"startScript"</SPAN>: { <SPAN class="hljs-attr">"scriptPath"</SPAN>: <SPAN class="hljs-string">"CopyDatabase.ps1"</SPAN>, <SPAN class="hljs-attr">"showWindow"</SPAN>: <SPAN class="hljs-literal">false</SPAN>, <SPAN class="hljs-attr">"waitForScriptToFinish"</SPAN>: <SPAN class="hljs-literal">true</SPAN>, <SPAN class="hljs-attr">"runOnce"</SPAN>: <SPAN class="hljs-literal">true</SPAN> } } ] } </CODE></PRE> <P>Let's take a look at the most important settings:</P> <P>&nbsp;</P> <UL> <LI> <P><CODE>id</CODE><SPAN>&nbsp;</SPAN>is the identifier of the application. We can find it by right clicking on the<SPAN>&nbsp;</SPAN><STRONG>Package.appxmanifest</STRONG><SPAN>&nbsp;</SPAN>file and choosing<SPAN>&nbsp;</SPAN><STRONG>View code</STRONG>. Look for the<SPAN>&nbsp;</SPAN><CODE>Application</CODE><SPAN>&nbsp;</SPAN>entry, which will look like this:</P> <P>&nbsp;</P> <PRE><CODE class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Application</SPAN> <SPAN class="hljs-attr">Id</SPAN>=<SPAN class="hljs-string">"App"</SPAN> <SPAN class="hljs-attr">Executable</SPAN>=<SPAN class="hljs-string">"$targetnametoken$.exe"</SPAN> <SPAN class="hljs-attr">EntryPoint</SPAN>=<SPAN class="hljs-string">"$targetentrypoint$"</SPAN>&gt;</SPAN> </CODE></PRE> <P>The identifier of the application is the value of the<SPAN>&nbsp;</SPAN><CODE>Id</CODE><SPAN>&nbsp;</SPAN>attribute. If you're using a standard Windows Application Packaging Project, its default value is always<SPAN>&nbsp;</SPAN><CODE>App</CODE>.</P> <P>&nbsp;</P> </LI> <LI> <P><CODE>executable</CODE><SPAN>&nbsp;</SPAN>is the main executable of the application. You need to specify the full path starting from the root of the package, in my case it's<SPAN>&nbsp;</SPAN><CODE>MyEmployees\MyEmployees.exe</CODE>.</P> </LI> <LI> <P>In order to redirect all the file system calls to the Current Working Directory, you need to specify it through the<SPAN>&nbsp;</SPAN><CODE>workingDirectory</CODE><SPAN>&nbsp;</SPAN>property. It must be the path of the folder which contains the main executable, in my case<SPAN>&nbsp;</SPAN><CODE>MyEmployees</CODE>.</P> </LI> </UL> <P>The other settings are dedicated to configure the scripting support. These are just some settings, but<SPAN>&nbsp;</SPAN><A href="#" target="_blank">in the documentation</A><SPAN>&nbsp;</SPAN>you'll find all of them.</P> <P>&nbsp;</P> <UL> <LI>We include the configuration of the script inside the<SPAN>&nbsp;</SPAN><CODE>startScript</CODE><SPAN>&nbsp;</SPAN>section, since we want to run it when the application starts.</LI> <LI><CODE>scriptPath</CODE><SPAN>&nbsp;</SPAN>is the name of the PowerShell script we have created in Step 1.</LI> <LI><CODE>showWindow</CODE><SPAN>&nbsp;</SPAN>is set to<SPAN>&nbsp;</SPAN><CODE>false</CODE>, since we want the script to be completely transparent to the user. We want him to just see the main application starting up. However, for testing purposes, it might be helpful to keep it to<SPAN>&nbsp;</SPAN><CODE>true</CODE>.</LI> <LI><CODE>waitForScriptToFinish</CODE><SPAN>&nbsp;</SPAN>is set to<SPAN>&nbsp;</SPAN><CODE>true</CODE>, since we don't want to start the MyEmployees application until the script has finished to copy the database, otherwise the data won't be loaded properly.</LI> <LI><CODE>runOnce</CODE><SPAN>&nbsp;</SPAN>is set to<SPAN>&nbsp;</SPAN><CODE>true</CODE><SPAN>&nbsp;</SPAN>because we don't want to repeat the script every time the application starts. We want to copy the database only the first time after the deployment.</LI> </UL> <P>&nbsp;</P> <H4 id="step-4-configure-the-manifest">Step 4: configure the manifest</H4> <P>The last step is to change the entry point of the application. When it starts, it doesn't have to launch anymore the<SPAN>&nbsp;</SPAN><CODE>MyEmployees.exe</CODE><SPAN>&nbsp;</SPAN>executable, but the Package Support Framework. It will be up to it to load the required DLLs and then execute the main application. This is why, in the<SPAN>&nbsp;</SPAN><CODE>config.json</CODE>, we have specified the main executable in the<SPAN>&nbsp;</SPAN><CODE>executable</CODE><SPAN>&nbsp;</SPAN>property. However, if we're using Visual Studio and the Windows Application Packaging Project, we can't do it directly in the<SPAN>&nbsp;</SPAN><CODE>Package.appxmanifest</CODE><SPAN>&nbsp;</SPAN>file. Even if we manually change it in the manifest, in fact, Visual Studio will always replace it with the entry point set in the WAP project during the MSIX generation. As such, we have first to generate the package and then make the changes.</P> <P>&nbsp;</P> <P>To generate the package, you just need to follow the normal procedure. Right click on the WAP project, choose<SPAN>&nbsp;</SPAN><STRONG>Publish → Create app package</STRONG><SPAN>&nbsp;</SPAN>and follow the wizard. Some important notes:</P> <P>&nbsp;</P> <UL> <LI> <P>You can skip the package signing at this point, because we'll need to sign it again after having changed the manifest.</P> </LI> <LI> <P>In the<SPAN>&nbsp;</SPAN><STRONG>Select and configure packages</STRONG><SPAN>&nbsp;</SPAN>section make sure to:</P> <UL> <LI>Set<SPAN>&nbsp;</SPAN><STRONG>Generate bundle</STRONG><SPAN>&nbsp;</SPAN>to<SPAN>&nbsp;</SPAN><STRONG>Never</STRONG>, since we want a plain MSIX package</LI> <LI>In the configuration mappings, include only the CPU architecture which matches the same one you've used for the Package Support Framework.<BR /><BR /><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="PublishPackage.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167731iD9BDA8F8CB22076D/image-size/large?v=v2&amp;px=999" role="button" title="PublishPackage.png" alt="PublishPackage.png" /></span></LI> </UL> </LI> </UL> <P>&nbsp;</P> <P><SPAN>Once you complete the wizard and you have a MSIX package, you need to edit it. The easiest way to do it is to have the&nbsp;</SPAN><A href="#" target="_blank">MSIX Packaging Tool</A><SPAN>&nbsp;installed&nbsp;</SPAN><A href="#" target="_blank">from the Microsoft Store</A><SPAN>. Once you have installed it, you can just right click on the MSIX package and choose&nbsp;</SPAN><STRONG>Edit with MSIX Packaging Tool</STRONG><SPAN>. As first thing we need to change the entry point, so click on the&nbsp;</SPAN><STRONG>Open file</STRONG><SPAN>&nbsp;button under the&nbsp;</SPAN><STRONG>Manifest file</STRONG><SPAN>&nbsp;section.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="MSIXPackagingTool.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167732i930B32B991BAA7DA/image-size/large?v=v2&amp;px=999" role="button" title="MSIXPackagingTool.png" alt="MSIXPackagingTool.png" /></span></SPAN></P> <P>&nbsp;</P> <P>The manifest will be opened in your favorite text editor and any change you're going to make will be automatically included back in the main package. Look again for the<SPAN>&nbsp;</SPAN><CODE>Application</CODE><SPAN>&nbsp;</SPAN>entry, which will look like this:</P> <P>&nbsp;</P> <PRE><CODE class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Application</SPAN> <SPAN class="hljs-attr">Id</SPAN>=<SPAN class="hljs-string">"App"</SPAN> <SPAN class="hljs-attr">Executable</SPAN>=<SPAN class="hljs-string">"MyEmployees\MyEmployees.exe"</SPAN> <SPAN class="hljs-attr">EntryPoint</SPAN>=<SPAN class="hljs-string">"Windows.FullTrustApplication"</SPAN>&gt;</SPAN> </CODE></PRE> <P>You must replace the<SPAN>&nbsp;</SPAN><CODE>Executable</CODE><SPAN>&nbsp;</SPAN>attribute with the path of the<SPAN>&nbsp;</SPAN><CODE>PsfLauncher64.exe</CODE><SPAN>&nbsp;</SPAN>file (or<SPAN>&nbsp;</SPAN><CODE>PsfLauncher32.exe</CODE><SPAN>&nbsp;</SPAN>if your application is 32-bit). This is how the manifest should look like after the change:</P> <P>&nbsp;</P> <PRE><CODE class="language-xml hljs"><SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Application</SPAN> <SPAN class="hljs-attr">Id</SPAN>=<SPAN class="hljs-string">"App"</SPAN> <SPAN class="hljs-attr">Executable</SPAN>=<SPAN class="hljs-string">"PsfLauncher64.exe"</SPAN> <SPAN class="hljs-attr">EntryPoint</SPAN>=<SPAN class="hljs-string">"Windows.FullTrustApplication"</SPAN>&gt;</SPAN> </CODE></PRE> <P>Save and close the text editor. Now we need to sign the package, otherwise we won't be able to install it. From the<SPAN>&nbsp;</SPAN><STRONG>Signing preference</STRONG><SPAN>&nbsp;</SPAN>dropdown choose the option which works best for you. For example, if you're used to sign your MSIX package with a certificate, choose the<SPAN>&nbsp;</SPAN><STRONG>Sign with a certificate (.pfx)</STRONG><SPAN>&nbsp;</SPAN>option and select if from your hard drive. Then provide the password in the new field that will appear. Regardless of the option you choose, once you have configured the signing you can generate an updated package by pressing the<SPAN>&nbsp;</SPAN><STRONG>Save</STRONG><SPAN>&nbsp;</SPAN>button. The tool will propose you to upgrade the version number. It isn't really required in our case since we aren't creating an update, so feel free to choose<SPAN>&nbsp;</SPAN><STRONG>No</STRONG>.</P> <P>&nbsp;</P> <H3 id="test-the-package">Test the package</H3> <P>That's it! Now double click on the MSIX package you have just created with the MSIX Packaging Tool and install it. At the end, Windows will launch automatically the application. If everything went well, you should see the list of employees displayed in the main window. And, as a confirm, you should find also the<SPAN>&nbsp;</SPAN><CODE>Employees.db</CODE><SPAN>&nbsp;</SPAN>file inside the<SPAN>&nbsp;</SPAN><CODE>C:\ProgramData\MyEmployees</CODE><SPAN>&nbsp;</SPAN>folder. All of this without changing the code of the main application.</P> <P>&nbsp;</P> <H3 id="what-about-the-appdata-folder">What about the AppData folder?</H3> <P>In some scenarios the data might be stored in the local AppData folder. Can we use the same approach also in this scenario? The answer is yes! You just need to take in consideration that the AppData folder is virtualized by Windows when an application is packaged as MSIX. This means that every time the application tries to read or write a file from the AppData folder, the operation is redirected to the local storage of the application. This approach helps Windows to maintain a clean installation. When the application is uninstalled, Windows will simply delete the local storage, without leaving leftovers behind.</P> <P>This means that if your application copies some files from the package to the AppData folder, the operation will work just fine but you won't actually find the files in the real AppData folder of the user, but you will have to look in the local folder of the application, which is stored in the<SPAN>&nbsp;</SPAN><CODE>%LOCALAPPDATA%\Packages</CODE><SPAN>&nbsp;</SPAN>folder. For example, in case of the MyEmployees applications, files would be copied in the<SPAN>&nbsp;</SPAN><CODE>%LOCALAPPDATA%\Local\Packages\MyEmployees_mfkd8f81tn1vy\LocalCache\Local</CODE><SPAN>&nbsp;</SPAN>folder.</P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>Having an application which comes with a default set of data that must be deployed to a folder like ProgramData or AppData is a common scenario. MSIX, out of the box, isn't able to directly support it, since all the files included in the package can be deployed only to a special folder where Windows keeps all the packaged apps. In this post we have seen a couple of options to overcome this limitation:</P> <P>&nbsp;</P> <UL> <LI>If we're talking about an application which is actively developed, we can just add some code to copy the files the first time the application launches.</LI> <LI>If you don't have the opportunity to make code changes, either because you don't own it or you're unable to them, you can leverage the Package Support Framework and the recently added scripting support. Thanks to a PowerShell script, you can copy the files you need in the destination folder only the first time the application starts after it has been deployed.</LI> </UL> <P>As usual, you will find the sample used in this blog post<SPAN>&nbsp;</SPAN><A href="#" target="_blank">on GitHub</A>.</P> <P>Happy coding!</P> Tue, 28 Jan 2020 19:13:35 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/msix-how-to-copy-data-outside-the-installation-folder-during-the/ba-p/1133602 Matteo Pagani 2020-01-28T19:13:35Z Host Custom UWP Controls in MFC MDI Project using XAML Islands https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/host-custom-uwp-controls-in-mfc-mdi-project-using-xaml-islands/ba-p/1130087 <H1 id="host-custom-uwp-controls-in-mfc-mdi-project-using-xaml-islands">Host Custom UWP Controls in MFC MDI Project using XAML Islands</H1> <P>This article explains how to modernize MFC MDI project with custom UWP Controls through XAML Islands. With custom UWP controls, it allows us to define control layout easily through XAML pages. Not only putting standard UWP controls into the custom control, we can also integrate other custom controls as well, such as latest WinUI controls. We will use a new Xaml Application project to bring the custom UWP controls into our MFC project. This <A href="#" target="_blank" rel="noopener">article</A> has mentioned how to use the new XAML application in Win32 C++ application, we will give more detailed steps for MFC project.</P> <P>Overall, in our MFC project, we will have two parts to demonstrate this method:</P> <UL> <LI>MFC MDI Project</LI> <LI>A companion UWP app project that defines a XamlApplication object.<BR />In this project, we will define a custom UWP control and export it so that MFC can use the custom UWP control.</LI> </UL> <H2 id="development-environment">Development Environment</H2> <UL> <LI>Visual Studio 2019 (16.3.6)</LI> <LI>Windows 10 1909 (18363.476)</LI> <LI>Windows 10 SDK (10.0.18362.1)</LI> </UL> <H2 id="configure-mfc-project">Configure MFC Project</H2> <OL type="1"> <LI> <P>Create MFC App in Visual Studio 2019, will name it MFCAPP</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="0.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167348i781633E431A9536A/image-size/large?v=v2&amp;px=999" role="button" title="0.png" alt="0.png" /></span></P> <P>Use below configuration to create the MFCAPP project</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="1.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167349i41F7B0095AADEB9C/image-size/large?v=v2&amp;px=999" role="button" title="1.png" alt="1.png" /></span></P> <P>click <STRONG>Finish</STRONG> Build and Run it, here is its default UI</P> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFC/2.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="2.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167350iD4329592FAF62E5E/image-size/large?v=v2&amp;px=999" role="button" title="2.png" alt="2.png" /></span></FIGCAPTION> </FIGURE> </LI> <LI> <P>In Solution Explorer, right-click the MFCAPP project node, click <STRONG>Retarget Project</STRONG>, select the <STRONG>10.0.18362.0</STRONG> or a later SDK release, and then click OK.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="3.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167351iE8C58CBB8639FD5C/image-size/medium?v=v2&amp;px=400" role="button" title="3.png" alt="3.png" /></span></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="4.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167352i98004B7D6884BEA0/image-size/medium?v=v2&amp;px=400" role="button" title="4.png" alt="4.png" /></span></P> </LI> <LI> <P>Install the Microsoft.Windows.CppWinRT NuGet package:</P> <OL type="a"> <LI>Right-click the project in Solution Explorer and choose <STRONG><EM>Manage NuGet Packages</EM></STRONG>.</LI> <LI>Select the Browse tab, search for the <STRONG>Microsoft.Windows.CppWinRT</STRONG> package, and install the latest version of this package.</LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFC/5.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="5.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167353i97CAB78D300F0E21/image-size/large?v=v2&amp;px=999" role="button" title="5.png" alt="5.png" /></span></FIGCAPTION> </FIGURE> <P>After install the nuget package, check the MFC project properties, you will notice its C++ version is ISO C++17, which is required by C++/WinRT:</P> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFC/6.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="6.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167354i6E18E4D53778DD37/image-size/large?v=v2&amp;px=999" role="button" title="6.png" alt="6.png" /></span></FIGCAPTION> </FIGURE> <P>Build this MFCApp, we can see winrt projected files are generated in the “Generated Files” folder:</P> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFC/7.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="7.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167356i09D3F8532E8B606A/image-size/large?v=v2&amp;px=999" role="button" title="7.png" alt="7.png" /></span></FIGCAPTION> </FIGURE> </LI> <LI> <P>Install the <STRONG>Microsoft.Toolkit.Win32.UI.SDK</STRONG> NuGet package:</P> <OL type="a"> <LI>In the NuGet Package Manager window, make sure that Include prerelease is selected.</LI> <LI>Select the Browse tab, search for the <STRONG>Microsoft.Toolkit.Win32.UI.SDK</STRONG> package, and install version v6.0.0 (or Later) of this package.</LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFC/8.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="8.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167357i987888C7C9B9A1F8/image-size/large?v=v2&amp;px=999" role="button" title="8.png" alt="8.png" /></span></FIGCAPTION> </FIGURE> </LI> <LI> <P>Install the <STRONG><EM>Microsoft.VCRTForwarders.140</EM></STRONG> nuget package as well. Running Custom UWP Control in this project will require VC libs.</P> <P><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/18.png" border="0" width="300" /></P> </LI> </OL> <H2 id="configure-uwp-project">Configure UWP Project</H2> <OL type="1"> <LI> <P>In Solution Explorer, right-click the solution node and select Add -&gt; New Project.</P> </LI> <LI> <P>Add a Blank App (C++/WinRT) project to your solution.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167360iEE6C68C35F30BB8D/image-size/medium?v=v2&amp;px=400" role="button" title="1.png" alt="1.png" /></span></P> <SPAN>Note: Please make sure this is C++/WinRT template. If cannot find it, you'll want to download and install the latest version of the&nbsp;</SPAN><U><A title="https://marketplace.visualstudio.com/items?itemname=cppwinrtteam.cppwinrt101804264" href="#" target="_blank" rel="noopener noreferrer">C++/WinRT Visual Studio Extension (VSIX)</A></U><SPAN>&nbsp;from the&nbsp;</SPAN><U><A title="https://marketplace.visualstudio.com/" href="#" target="_blank" rel="noopener noreferrer">Visual Studio Marketplace</A></U><SPAN>.</SPAN></LI> <LI> <P>Give it a name <STRONG><EM>MyApp</EM></STRONG>, and create it, Make sure the target version and minimum version are <STRONG><EM>both</EM></STRONG> set to <STRONG><EM>Windows 10, version 1903</EM></STRONG> or later.</P> </LI> <LI> <P>Right click the MyApp and open its properties, make sure its C++/WinRT configuration is as below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="3.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167362iFF88DC17956377B5/image-size/large?v=v2&amp;px=999" role="button" title="3.png" alt="3.png" /></span></P> </LI> <LI> <P>Change its output type from .EXE to Dynamic Library</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="4.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167363i73A4CBA4D253E329/image-size/large?v=v2&amp;px=999" role="button" title="4.png" alt="4.png" /></span></P> </LI> <LI> <P>Save a dummy.exe into the <STRONG><EM>MyApp</EM></STRONG> folder. It doesn’t need to be a real exe, just input “dummy exe file” in notepad, and save it as <STRONG><EM>dummy.exe</EM></STRONG>.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="5.png" style="width: 200px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167364i4711A20C146B90C0/image-size/small?v=v2&amp;px=200" role="button" title="5.png" alt="5.png" /></span></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="6.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167365iD271467D6B93EC5F/image-size/medium?v=v2&amp;px=400" role="button" title="6.png" alt="6.png" /></span></P> <P>Make sure the Content property of dummy.exe is <STRONG><EM>True</EM></STRONG>.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="6.1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167366iC77BE3EDBB85F2E2/image-size/medium?v=v2&amp;px=400" role="button" title="6.1.png" alt="6.1.png" /></span></P> </LI> <LI> <P>Now edit Package.appxmanifest, change the Executable attribute to “dummy.exe”</P> </LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/7.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="7.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167368i0519876256EEEF12/image-size/large?v=v2&amp;px=999" role="button" title="7.png" alt="7.png" /></span></FIGCAPTION> </FIGURE> <OL start="8" type="1"> <LI> <P>Right click the <STRONG><EM>MyApp</EM></STRONG> project, select <STRONG><EM>Unload Project</EM></STRONG></P> </LI> <LI> <P>Right click the <STRONG><EM>MyApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Edit MyApp.vcxproj</EM></STRONG></P> </LI> <LI> <P>Add below properties to the <STRONG><EM>MyApp.vcxproj</EM></STRONG> project file:</P> </LI> </OL> <DIV id="cb1" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;PropertyGroup</SPAN><SPAN class="ot"> Label=</SPAN><SPAN class="st">"Globals"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;WindowsAppContainer&gt;</SPAN>true<SPAN class="kw">&lt;/WindowsAppContainer&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppxGeneratePriEnabled&gt;</SPAN>true<SPAN class="kw">&lt;/AppxGeneratePriEnabled&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;ProjectPriIndexName&gt;</SPAN>App<SPAN class="kw">&lt;/ProjectPriIndexName&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppxPackage&gt;</SPAN>true<SPAN class="kw">&lt;/AppxPackage&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P>For example:</P> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/8.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="8.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167369i7D9AEF4203B7BA42/image-size/large?v=v2&amp;px=999" role="button" title="8.png" alt="8.png" /></span></FIGCAPTION> </FIGURE> <OL start="11" type="1"> <LI> <P>Right click the <STRONG><EM>MyApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Reload Project</EM></STRONG>.</P> </LI> <LI> <P>Right click <STRONG><EM>Mainpage.xml</EM></STRONG>, select <STRONG><EM>Remove</EM></STRONG>. And then click <STRONG><EM>Delete</EM></STRONG></P> </LI> <LI> <P>Copy App.Xaml, App.cpp, App.h, App.idl contents to overwrite current ones:</P> </LI> </OL> <P><STRONG><EM>App.Xaml</EM></STRONG></P> <DIV id="cb2" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;Toolkit:XamlApplication</SPAN></SPAN> <SPAN><SPAN class="ot"> x:Class=</SPAN><SPAN class="st">"MyApp.App"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:x=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:local=</SPAN><SPAN class="st">"using:MyApp"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:Toolkit=</SPAN><SPAN class="st">"using:Microsoft.Toolkit.Win32.UI.XamlHost"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:d=</SPAN><SPAN class="st">"http://schemas.microsoft.com/expression/blend/2008"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:mc=</SPAN><SPAN class="st">"http://schemas.openxmlformats.org/markup-compatibility/2006"</SPAN></SPAN> <SPAN><SPAN class="ot"> RequestedTheme=</SPAN><SPAN class="st">"Light"</SPAN></SPAN> <SPAN><SPAN class="ot"> mc:Ignorable=</SPAN><SPAN class="st">"d"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/Toolkit:XamlApplication&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P><STRONG><EM>App.cpp</EM></STRONG></P> <DIV id="cb3" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"pch.h"</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"App.h"</SPAN></SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> winrt;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Xaml;</SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::implementation</SPAN> <SPAN>{</SPAN> <SPAN> App::App()</SPAN> <SPAN> {</SPAN> <SPAN> Initialize();</SPAN> <SPAN> AddRef();</SPAN> <SPAN> <SPAN class="va">m_inner</SPAN>.as&lt;::IUnknown&gt;()-&gt;Release();</SPAN> <SPAN> }</SPAN> <SPAN> App::~App()</SPAN> <SPAN> {</SPAN> <SPAN> Close();</SPAN> <SPAN> }</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> <P><STRONG><EM>App.h</EM></STRONG></P> <DIV id="cb4" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="co">//</SPAN></SPAN> <SPAN><SPAN class="co">// Declaration of the App class.</SPAN></SPAN> <SPAN><SPAN class="co">//</SPAN></SPAN> <SPAN><SPAN class="pp">#pragma once</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"App.g.h"</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"App.base.h"</SPAN></SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::implementation</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="kw">class</SPAN> App : <SPAN class="kw">public</SPAN> AppT2&lt;App&gt;</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="kw">public</SPAN>:</SPAN> <SPAN> App();</SPAN> <SPAN> ~App();</SPAN> <SPAN> };</SPAN> <SPAN>}</SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::factory_implementation</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="kw">class</SPAN> App : <SPAN class="kw">public</SPAN> AppT&lt;App, implementation::App&gt;</SPAN> <SPAN> {</SPAN> <SPAN> };</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> <P><STRONG><EM>App.idl</EM></STRONG></P> <DIV id="cb5" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="kw">namespace</SPAN> MyApp</SPAN> <SPAN>{</SPAN> <SPAN> [default_interface]</SPAN> <SPAN> runtimeclass App: Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication</SPAN> <SPAN> {</SPAN> <SPAN> App();</SPAN> <SPAN> }</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> <OL start="14" type="1"> <LI>Create app.base.h in this project, and use below content:</LI> </OL> <P><STRONG><EM>app.base.h</EM></STRONG></P> <DIV id="cb6" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#pragma once</SPAN></SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::implementation</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="kw">template</SPAN> &lt;<SPAN class="kw">typename</SPAN> D, <SPAN class="kw">typename</SPAN>... I&gt;</SPAN> <SPAN> <SPAN class="kw">struct</SPAN> App_baseWithProvider : <SPAN class="kw">public</SPAN> App_base&lt;D, ::winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider&gt;</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="kw">using</SPAN> IXamlType = ::winrt::Windows::UI::Xaml::Markup::IXamlType;</SPAN> <SPAN> IXamlType GetXamlType(::winrt::Windows::UI::Xaml::Interop::TypeName <SPAN class="at">const</SPAN>&amp; type)</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">return</SPAN> AppProvider()-&gt;GetXamlType(type);</SPAN> <SPAN> }</SPAN> <SPAN> IXamlType GetXamlType(::winrt::hstring <SPAN class="at">const</SPAN>&amp; fullName)</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">return</SPAN> AppProvider()-&gt;GetXamlType(fullName);</SPAN> <SPAN> }</SPAN> <SPAN> ::winrt::com_array&lt;::winrt::Windows::UI::Xaml::Markup::XmlnsDefinition&gt; GetXmlnsDefinitions()</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">return</SPAN> AppProvider()-&gt;GetXmlnsDefinitions();</SPAN> <SPAN> }</SPAN> <SPAN> <SPAN class="kw">private</SPAN>:</SPAN> <SPAN> <SPAN class="dt">bool</SPAN> _contentLoaded{ <SPAN class="kw">false</SPAN> };</SPAN> <SPAN> <SPAN class="bu">std::</SPAN>shared_ptr&lt;XamlMetaDataProvider&gt; _appProvider;</SPAN> <SPAN> <SPAN class="bu">std::</SPAN>shared_ptr&lt;XamlMetaDataProvider&gt; AppProvider()</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">if</SPAN> (!_appProvider)</SPAN> <SPAN> {</SPAN> <SPAN> _appProvider = <SPAN class="bu">std::</SPAN>make_shared&lt;XamlMetaDataProvider&gt;();</SPAN> <SPAN> }</SPAN> <SPAN> <SPAN class="cf">return</SPAN> _appProvider;</SPAN> <SPAN> }</SPAN> <SPAN> };</SPAN> <SPAN> <SPAN class="kw">template</SPAN> &lt;<SPAN class="kw">typename</SPAN> D, <SPAN class="kw">typename</SPAN>... I&gt;</SPAN> <SPAN> <SPAN class="kw">using</SPAN> AppT2 = App_baseWithProvider&lt;D, I...&gt;;</SPAN> <SPAN>}</SPAN> </CODE></PRE> </DIV> <OL start="14" type="1"> <LI> <P>Install <A href="#" target="_blank" rel="noopener">Microsoft.Toolkit.Win32.UI.XamlApplication</A> Nuget package.</P> </LI> <LI> <P>If you build <STRONG><EM>MyApp</EM></STRONG> now, it should create MyApp.dll without any error.</P> </LI> </OL> <H2 id="centralize-output-input-and-cwinrt-files-in-solution">Centralize Output, Input and C++/WinRT files in Solution</H2> <P>This step is necessary for our next steps because we need to include winrt header files in different projects properly, and MFCApp also needs to reference MyApp resource files.</P> <OL type="1"> <LI>Add a new Solution.Props file by right clicking the solution node, and select Add -&gt; New Item:</LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/13.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="13.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167370i9317B7F41B450E97/image-size/large?v=v2&amp;px=999" role="button" title="13.png" alt="13.png" /></span></FIGCAPTION> </FIGURE> <OL start="2" type="1"> <LI>Use below content to overwrite the Solution.Props:</LI> </OL> <DIV id="cb7" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;?xml</SPAN> version="1.0" encoding="utf-8"<SPAN class="kw">?&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;Project</SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"http://schemas.microsoft.com/developer/msbuild/2003"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;IntDir&gt;</SPAN>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\<SPAN class="kw">&lt;/IntDir&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;OutDir&gt;</SPAN>$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(MSBuildProjectName)\<SPAN class="kw">&lt;/OutDir&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;GeneratedFilesDir&gt;</SPAN>$(IntDir)Generated Files\<SPAN class="kw">&lt;/GeneratedFilesDir&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/Project&gt;</SPAN></SPAN></CODE></PRE> </DIV> <OL start="3" type="1"> <LI> <P>Click Views -&gt; Other Windows -&gt; Property Manager</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="14.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167371i99C4B3F810979B02/image-size/medium?v=v2&amp;px=400" role="button" title="14.png" alt="14.png" /></span></P> </LI> <LI> <P>Right click <STRONG><EM>MFCApp</EM></STRONG>, select <STRONG><EM>Add Existing Property Sheet</EM></STRONG>, add the new <STRONG><EM>solution.props</EM></STRONG> file</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="15.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167372iA5094D41B6ABB36A/image-size/medium?v=v2&amp;px=400" role="button" title="15.png" alt="15.png" /></span></P> </LI> <LI> <P>Repeat the step 4. for <STRONG><EM>MyApp</EM></STRONG>. We can close the Property Manager window now.</P> </LI> <LI> <P>Right click the project node <STRONG><EM>MFCApp</EM></STRONG>, select Properties. Set</P> </LI> </OL> <P>Output Directory: $(OutDir)</P> <P>Intermidia Directory: $(IntDir)</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="12.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167373i654BE14390759307/image-size/large?v=v2&amp;px=999" role="button" title="12.png" alt="12.png" /></span></P> <OL start="7" type="1"> <LI> <P>Repeat the step 6 for <STRONG><EM>MyApp</EM></STRONG>.</P> </LI> <LI> <P>Right click the Solution node, and choose <STRONG><EM>Project Dependencies</EM></STRONG>, make sure MFCApp depends on MyApp:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="16.png" style="width: 373px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167374i0A604407051580F8/image-size/medium?v=v2&amp;px=400" role="button" title="16.png" alt="16.png" /></span></P> </LI> <LI> <P>Rebuild the whole solution, it should work without errors.</P> </LI> </OL> <H2 id="add-uwp-custom-xaml-control-to-myapp">Add UWP Custom XAML Control to MyApp</H2> <OL type="1"> <LI>Right click <STRONG><EM>MyApp</EM></STRONG>, select Add -&gt; New Item</LI> <LI>Create <STRONG><EM>Blank User Conrol (C++/WinRT)</EM></STRONG>, here we call it TreeViewUserControl:</LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/10.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="10.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167375i1E33193771A456A4/image-size/large?v=v2&amp;px=999" role="button" title="10.png" alt="10.png" /></span></FIGCAPTION> </FIGURE> <H2 id="integrate-custom-xaml-control-in-mfcapp">Integrate Custom XAML Control in MFCApp</H2> <OL type="1"> <LI>Add one <STRONG>app.manifest</STRONG> in your project with below content to register custom control type:</LI> </OL> <DIV id="cb8" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;?xml</SPAN> version="1.0" encoding="utf-8" standalone="yes"<SPAN class="kw">?&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;assembly</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:asm.v1"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:asmv3=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:asm.v3"</SPAN></SPAN> <SPAN><SPAN class="ot"> manifestVersion=</SPAN><SPAN class="st">"1.0"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;asmv3:file</SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.dll"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;activatableClass</SPAN></SPAN> <SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.App"</SPAN></SPAN> <SPAN><SPAN class="ot"> threadingModel=</SPAN><SPAN class="st">"both"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:winrt.v1"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;activatableClass</SPAN></SPAN> <SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.XamlMetadataProvider"</SPAN></SPAN> <SPAN><SPAN class="ot"> threadingModel=</SPAN><SPAN class="st">"both"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:winrt.v1"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;activatableClass</SPAN></SPAN> <SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.TreeViewUserControl"</SPAN></SPAN> <SPAN><SPAN class="ot"> threadingModel=</SPAN><SPAN class="st">"both"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:winrt.v1"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/asmv3:file&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/assembly&gt;</SPAN></SPAN></CODE></PRE> </DIV> <OL start="2" type="1"> <LI> <P>Now the project structure is like as below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="12.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167376i5303F7EAD8E4D306/image-size/medium?v=v2&amp;px=400" role="button" title="12.png" alt="12.png" /></span></P> </LI> <LI> <P>Right click the Win32 Project <STRONG><EM>MFCApp</EM></STRONG>, select <STRONG><EM>Unload Project</EM></STRONG></P> </LI> <LI> <P>Right click the <STRONG><EM>MFCApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Edit MFCApp.vcxproj</EM></STRONG></P> </LI> <LI> <P>Remove the three lines from the <STRONG><EM>MFCApp.vcxproj</EM></STRONG> project file:</P> </LI> </OL> <DIV id="cb9" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;Manifest</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"app.manifest"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/ItemGroup&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P>Add below properties to the <STRONG><EM>MFCApp.vcxproj</EM></STRONG> project file before the <STRONG><EM>“&lt;Import Project=”“$(VCTargetsPath).Cpp.targets” /&gt;"</EM></STRONG> line:</P> <DIV id="cb10" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN> <SPAN class="kw">&lt;ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;Manifest</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"app.manifest"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppxManifest</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\AppxManifest.xml"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppProjectName&gt;</SPAN>MyApp<SPAN class="kw">&lt;/AppProjectName&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppIncludeDirectories&gt;</SPAN>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(AppProjectName)\;$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(AppProjectName)\Generated Files\;<SPAN class="kw">&lt;/AppIncludeDirectories&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;NativeReferenceFile</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\*.xbf"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;DeploymentContent&gt;</SPAN>true<SPAN class="kw">&lt;/DeploymentContent&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;CopyToOutputDirectory&gt;</SPAN>PreserveNewest<SPAN class="kw">&lt;/CopyToOutputDirectory&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/NativeReferenceFile&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;NativeReferenceFile</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\*.dll"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;DeploymentContent&gt;</SPAN>true<SPAN class="kw">&lt;/DeploymentContent&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;CopyToOutputDirectory&gt;</SPAN>PreserveNewest<SPAN class="kw">&lt;/CopyToOutputDirectory&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/NativeReferenceFile&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;NativeReferenceFile</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\resources.pri"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;DeploymentContent&gt;</SPAN>true<SPAN class="kw">&lt;/DeploymentContent&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;CopyToOutputDirectory&gt;</SPAN>PreserveNewest<SPAN class="kw">&lt;/CopyToOutputDirectory&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/NativeReferenceFile&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="er">&lt;</SPAN>------Right Here---------&gt;</SPAN> <SPAN> <SPAN class="kw">&lt;Import</SPAN><SPAN class="ot"> Project=</SPAN><SPAN class="st">"$(VCTargetsPath)\Microsoft.Cpp.targets"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN></CODE></PRE> </DIV> <OL start="6" type="1"> <LI> <P>Right click the <STRONG><EM>MFCApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Reload Project</EM></STRONG>.</P> </LI> <LI> <P>Right click <STRONG><EM>MFCApp</EM></STRONG>, select <STRONG><EM>Properties</EM></STRONG>, setup <STRONG><EM>$(AppIncludeDirectories)</EM></STRONG> as a part of include file path, this macro has been defined in the above project file:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="17.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167377iD516CE1E12CD539C/image-size/large?v=v2&amp;px=999" role="button" title="17.png" alt="17.png" /></span></P> </LI> <LI> <P>Set <STRONG><EM>“Per Monitor DPI Aware”</EM></STRONG> for <STRONG><EM>DPI Awareness</EM></STRONG> otherwise you may be not able to start this MFCApp when it is <STRONG><EM>“High DPI Aware”</EM></STRONG> and hit configuration error in Manifest: <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="19.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167378iA9F1AA239C7E1B2D/image-size/large?v=v2&amp;px=999" role="button" title="19.png" alt="19.png" /></span></P> </LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/19.png" border="0" alt="" /> <FIGCAPTION></FIGCAPTION> </FIGURE> <OL start="9" type="1"> <LI> <P>Open pch.h, add below code to include necessary winrt header files:</P> <DIV id="cb11" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#pragma push_macro("GetCurrentTime")</SPAN></SPAN> <SPAN><SPAN class="pp">#pragma push_macro("TRY")</SPAN></SPAN> <SPAN><SPAN class="pp">#undef GetCurrentTime</SPAN></SPAN> <SPAN><SPAN class="pp">#undef TRY</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.Foundation.Collections.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.system.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/windows.ui.xaml.hosting.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;windows.ui.xaml.hosting.desktopwindowxamlsource.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/windows.ui.xaml.controls.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.ui.xaml.media.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.UI.Core.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/myapp.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#pragma pop_macro("TRY")</SPAN></SPAN> <SPAN><SPAN class="pp">#pragma pop_macro("GetCurrentTime")</SPAN></SPAN></CODE></PRE> </DIV> <P>Regarding the reason of using “GetCurrentTime” and “TRY” macros, please refer to: <A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/faq</A></P> <P>Using winrt namespaces in MFCAPPView.h and MFCAPP.h</P> <DIV id="cb12" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> winrt;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Composition;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Xaml::Hosting;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::Foundation::Numerics;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Xaml::Controls;</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Declare hostApp in <STRONG><EM>MFCApp.h</EM></STRONG></P> </LI> </OL> <DIV id="cb13" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="kw">public</SPAN>:</SPAN> <SPAN> winrt::MyApp::App hostApp{ <SPAN class="kw">nullptr</SPAN> };</SPAN></CODE></PRE> </DIV> <OL start="11" type="1"> <LI>Initalize hostApp right before new CMainFrame in <STRONG><EM>CMFCAppApp::InitInstance()</EM></STRONG> in MFCApp.CPP</LI> </OL> <DIV id="cb14" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN> hostApp = winrt::MyApp::App{};</SPAN> <SPAN> &lt;---Right Here---&gt;</SPAN> <SPAN> <SPAN class="co">// create main MDI Frame window</SPAN></SPAN> <SPAN> CMainFrame* pMainFrame = <SPAN class="kw">new</SPAN> CMainFrame;</SPAN></CODE></PRE> </DIV> <OL start="11" type="1"> <LI>Declare _desktopWindowXamlSource and our custom control in MFCAppView.h</LI> </OL> <DIV id="cb15" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="kw">private</SPAN>:</SPAN> <SPAN> winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _desktopWindowXamlSource{ <SPAN class="kw">nullptr</SPAN> };</SPAN> <SPAN> winrt::MyApp::TreeViewUserControl _treeViewUserControl{ <SPAN class="kw">nullptr</SPAN> };</SPAN></CODE></PRE> </DIV> <OL start="12" type="1"> <LI>Initialize _desktopWindowXamlSource and custom control in MFCAppView.cpp</LI> </OL> <DIV id="cb16" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="cf">if</SPAN> (_desktopWindowXamlSource == <SPAN class="kw">nullptr</SPAN>)</SPAN> <SPAN>{ </SPAN> <SPAN> _desktopWindowXamlSource = DesktopWindowXamlSource{};</SPAN> <SPAN> <SPAN class="kw">auto</SPAN> interop = _desktopWindowXamlSource.as&lt;IDesktopWindowXamlSourceNative&gt;();</SPAN> <SPAN> check_hresult(interop-&gt;AttachToWindow(GetSafeHwnd()));</SPAN> <SPAN> HWND xamlHostHwnd = NULL;</SPAN> <SPAN> check_hresult(interop-&gt;get_WindowHandle(&amp;xamlHostHwnd));</SPAN> <SPAN> _treeViewUserControl = winrt::MyApp::TreeViewUserControl();</SPAN> <SPAN> _desktopWindowXamlSource.Content(_treeViewUserControl);</SPAN> <SPAN> RECT windowRect;</SPAN> <SPAN> GetWindowRect(&amp;windowRect);</SPAN> <SPAN> ::SetWindowPos(xamlHostHwnd, NULL, <SPAN class="dv">0</SPAN>, <SPAN class="dv">0</SPAN>, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_SHOWWINDOW);</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> <OL start="13" type="1"> <LI> <P>Clean up resources when the view is disconstructed in MFCAppView.cpp</P> <DIV id="cb17" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN>CMFCAppView::~CMFCAppView()</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="cf">if</SPAN> (_desktopWindowXamlSource != <SPAN class="kw">nullptr</SPAN>)</SPAN> <SPAN> {</SPAN> <SPAN> _desktopWindowXamlSource.Close();</SPAN> <SPAN> _desktopWindowXamlSource = <SPAN class="kw">nullptr</SPAN>;</SPAN> <SPAN> }</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Add AdjustLayout function to make XAML content layout properly in MFCAppView.cpp :</P> </LI> </OL> <PRE><CODE>```C++ void CMFCAppView::AdjustLayout() { if (_desktopWindowXamlSource != nullptr) { auto interop = _desktopWindowXamlSource.as&lt;IDesktopWindowXamlSourceNative&gt;(); HWND xamlHostHwnd = NULL; check_hresult(interop-&gt;get_WindowHandle(&amp;xamlHostHwnd)); RECT windowRect; GetWindowRect(&amp;windowRect); ::SetWindowPos(xamlHostHwnd, NULL, 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_SHOWWINDOW); } } ``` Don't forget to declare it in MFCAppView.h: ```C++ public: void AdjustLayout(); ```</CODE></PRE> <OL start="15" type="1"> <LI> <P>Right click the MFCApp project, select <STRONG>Class Wizard</STRONG></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="9.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167379iAC8A42F2A07337D9/image-size/medium?v=v2&amp;px=400" role="button" title="9.png" alt="9.png" /></span></P> <P>Add a handler to handle WM_SIZE so that when view size changes we can handle it:</P> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFC/10.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="10.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167380i09E5CC2D3B241224/image-size/large?v=v2&amp;px=999" role="button" title="10.png" alt="10.png" /></span></FIGCAPTION> </FIGURE> </LI> <LI> <P>Modify the OnSize method handler:</P> </LI> </OL> <PRE><CODE>```C++ void CMFCAppView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); AdjustLayout(); } ```</CODE></PRE> <OL start="17" type="1"> <LI>Now you can build and run this MFCApp. It should display a button in the central of view window:</LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/21.gif" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="21.gif" style="width: 745px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167381iF2614F3937B6530F/image-size/large?v=v2&amp;px=999" role="button" title="21.gif" alt="21.gif" /></span></FIGCAPTION> </FIGURE> <H2 id="using-winui-in-uwp-custom-control-in-myapp-uwp-project">Using WinUI in UWP Custom Control in MyApp UWP Project</H2> <OL type="1"> <LI>In MyApp, let’s add the <STRONG><EM>Microsoft.UI.Xaml</EM></STRONG> nuget package:</LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/22.png" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="22.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167382iC0BA586A67E97EFC/image-size/large?v=v2&amp;px=999" role="button" title="22.png" alt="22.png" /></span></FIGCAPTION> </FIGURE> <BLOCKQUOTE> <P>[!NOTE] It is possible some version of WinUI nuget package doesn’t create Microsoft.UI.Xaml.Controls class registering info into AppxManifest.xml, which is required by MFCApp later. This version used above works well. If you found MFCApp failed to run with “Class is not registered” error, please try this version.</P> </BLOCKQUOTE> <OL start="2" type="1"> <LI> <P>Modify App.Xaml, TreeViewUserControl.Xaml, pch.h and TreeViewUserContro.cpp as below:</P> <BLOCKQUOTE> <P>For detailed reasons on modifying these files, can refer to this <A href="#" target="_blank" rel="noopener">article</A></P> </BLOCKQUOTE> </LI> </OL> <P>Add the Windows UI (WinUI) Theme Resources to <STRONG><EM>App.Xmal</EM></STRONG></P> <DIV id="cb20" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;Toolkit:XamlApplication</SPAN></SPAN> <SPAN><SPAN class="ot"> x:Class=</SPAN><SPAN class="st">"MyApp.App"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:x=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:local=</SPAN><SPAN class="st">"using:MyApp"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:Toolkit=</SPAN><SPAN class="st">"using:Microsoft.Toolkit.Win32.UI.XamlHost"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:MSMarkup=</SPAN><SPAN class="st">"using:Microsoft.UI.Xaml.Markup"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:d=</SPAN><SPAN class="st">"http://schemas.microsoft.com/expression/blend/2008"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:mc=</SPAN><SPAN class="st">"http://schemas.openxmlformats.org/markup-compatibility/2006"</SPAN></SPAN> <SPAN><SPAN class="ot"> RequestedTheme=</SPAN><SPAN class="st">"Light"</SPAN></SPAN> <SPAN><SPAN class="ot"> mc:Ignorable=</SPAN><SPAN class="st">"d"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;Toolkit:XamlApplication.Resources&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;XamlControlsResources</SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"using:Microsoft.UI.Xaml.Controls"</SPAN><SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/Toolkit:XamlApplication.Resources&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/Toolkit:XamlApplication&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P>Add WinUI reference and WinUI TreeView control in <STRONG><EM>TreeViewUserControl.Xaml</EM></STRONG></P> <DIV id="cb21" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;UserControl</SPAN></SPAN> <SPAN><SPAN class="ot"> x:Class=</SPAN><SPAN class="st">"MyApp.TreeViewUserControl"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:x=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:local=</SPAN><SPAN class="st">"using:MyApp"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:muxc=</SPAN><SPAN class="st">"using:Microsoft.UI.Xaml.Controls"</SPAN> </SPAN> <SPAN><SPAN class="ot"> xmlns:d=</SPAN><SPAN class="st">"http://schemas.microsoft.com/expression/blend/2008"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:mc=</SPAN><SPAN class="st">"http://schemas.openxmlformats.org/markup-compatibility/2006"</SPAN></SPAN> <SPAN><SPAN class="ot"> mc:Ignorable=</SPAN><SPAN class="st">"d"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;StackPanel</SPAN><SPAN class="ot"> Orientation=</SPAN><SPAN class="st">"Horizontal"</SPAN><SPAN class="ot"> HorizontalAlignment=</SPAN><SPAN class="st">"Center"</SPAN><SPAN class="ot"> VerticalAlignment=</SPAN><SPAN class="st">"Center"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;Button</SPAN><SPAN class="ot"> x:Name=</SPAN><SPAN class="st">"Button"</SPAN><SPAN class="ot"> Click=</SPAN><SPAN class="st">"ClickHandler"</SPAN><SPAN class="kw">&gt;</SPAN>Click Me<SPAN class="kw">&lt;/Button&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;muxc:TreeView</SPAN><SPAN class="ot"> x:Name=</SPAN><SPAN class="st">"WinUITreeView"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;muxc:TreeView.RootNodes&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;muxc:TreeViewNode</SPAN><SPAN class="ot"> Content=</SPAN><SPAN class="st">"Flavors"</SPAN></SPAN> <SPAN><SPAN class="ot"> IsExpanded=</SPAN><SPAN class="st">"True"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;muxc:TreeViewNode.Children&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;muxc:TreeViewNode</SPAN><SPAN class="ot"> Content=</SPAN><SPAN class="st">"Vanilla"</SPAN><SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;muxc:TreeViewNode</SPAN><SPAN class="ot"> Content=</SPAN><SPAN class="st">"Strawberry"</SPAN><SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;muxc:TreeViewNode</SPAN><SPAN class="ot"> Content=</SPAN><SPAN class="st">"Chocolate"</SPAN><SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/muxc:TreeViewNode.Children&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/muxc:TreeViewNode&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/muxc:TreeView.RootNodes&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/muxc:TreeView&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/StackPanel&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/UserControl&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P>Include WinUI winrt header files in <STRONG><EM>pch.h</EM></STRONG></P> <DIV id="cb22" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"winrt/Microsoft.UI.Xaml.Controls.h"</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"winrt/Microsoft.UI.Xaml.XamlTypeInfo.h"</SPAN></SPAN></CODE></PRE> </DIV> <P><STRONG><EM>TreeViewUserControl.cpp</EM></STRONG></P> <DIV id="cb23" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="dt">void</SPAN> TreeViewUserControl::ClickHandler(IInspectable <SPAN class="at">const</SPAN>&amp;, RoutedEventArgs <SPAN class="at">const</SPAN>&amp;)</SPAN> <SPAN> {</SPAN> <SPAN> Button().Content(box_value(<SPAN class="st">L"Clicked"</SPAN>));</SPAN> <SPAN> winrt::Microsoft::UI::Xaml::Controls::TreeViewNode tn = winrt::Microsoft::UI::Xaml::Controls::TreeViewNode{};</SPAN> <SPAN> tn.Content(winrt::box_value(<SPAN class="st">L"Clicked"</SPAN>));</SPAN> <SPAN> WinUITreeView().RootNodes().First().Current().Children().Append(tn);</SPAN> <SPAN> }</SPAN></CODE></PRE> </DIV> <OL start="3" type="1"> <LI>Build and run MFCApp, if steps have been taken exactly as above, it will show as below:</LI> </OL> <FIGURE><IMG src="https://techcommunity.microsoft.com/..https://techcommunity.microsoft.com/images/MFCCustomControl/23.gif" border="0" alt="" /> <FIGCAPTION><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="23.gif" style="width: 745px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167383iD458144BF092A07C/image-size/large?v=v2&amp;px=999" role="button" title="23.gif" alt="23.gif" /></span></FIGCAPTION> </FIGURE> <H2 id="wrap-up">Wrap Up</H2> <P>This article gives detailed steps on how to leverage XamplApplication to host custom XAML control in document view of traditional MFC Mulitple Document Interface project, and the important thing is you can use WinUI to modernize the MFC application now. The whole smaple solution can be found from this repo: <A href="#" target="_blank" rel="noopener">https://github.com/freistli/ModernizeApp/tree/master/MFC/MFCAppWinUI</A></P> Wed, 29 Apr 2020 15:38:45 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/host-custom-uwp-controls-in-mfc-mdi-project-using-xaml-islands/ba-p/1130087 freistli 2020-04-29T15:38:45Z Host Custom UWP Controls in C++ Win32 Project using XAML Islands https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/host-custom-uwp-controls-in-c-win32-project-using-xaml-islands/ba-p/1129823 <H1 id="host-custom-uwp-controls-in-c-win32-project-using-xaml-islands">Host Custom UWP Controls in C++ Win32 Project using XAML Islands</H1> <P>This article explains how to modernize C++ Win32 project with custom UWP Controls through XAML Islands in detail. With custom UWP controls, it allows us to define control layout easily through XAML pages. Not only putting standard UWP controls into the custom control, we can also integrate other custom controls as well, such as latest WinUI controls. We will use a new Xaml Application project to bring the custom UWP controls into our C++ Win32 project. This <A href="#" target="_blank" rel="noopener">article</A> has mentioned how to use the new XAML application in Win32 C++ application, we will give more detailed steps.</P> <P>Overall, in our solution, we will have two parts to demonstrate this method:</P> <UL> <LI> <P>C++ Win32 Project</P> </LI> <LI> <P>A companion UWP app project that defines a XamlApplication object.</P> <P>In this project, we will define a custom UWP control and export it so that C++ Win32 can use the custom UWP control.</P> </LI> </UL> <H2 id="development-environment">Development Environment</H2> <UL> <LI>Visual Studio 2019 (16.3.6)</LI> <LI>Windows 10 1909 (18363.476)</LI> <LI>Windows 10 SDK (10.0.18362.1)</LI> </UL> <H2 id="configure-c-win32-project">Configure C++ Win32 Project</H2> <OL type="1"> <LI> <P>Create C++ Win32 in Visual Studio 2019, will name it SimpleApp</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="01.png" style="width: 492px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167310i1A06B85A36604467/image-dimensions/492x87?v=v2" width="492" height="87" role="button" title="01.png" alt="01.png" /></span></P> </LI> <LI> <P>In Solution Explorer, right-click the Simple project node, click <STRONG>Retarget Project</STRONG>, select the <STRONG>10.0.18362.0</STRONG> or a later SDK release, and then click OK.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="3.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167311i02F2C3C0C734DE84/image-size/medium?v=v2&amp;px=400" role="button" title="3.png" alt="3.png" /></span></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="02.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167312i40F8211E31BA806A/image-size/medium?v=v2&amp;px=400" role="button" title="02.png" alt="02.png" /></span></P> </LI> <LI> <P>Install the <STRONG><EM>Microsoft.Windows.CppWinRT</EM></STRONG> NuGet package:</P> <OL type="a"> <LI> <P>Right-click the project in Solution Explorer and choose <STRONG><EM>Manage NuGet Packages</EM></STRONG>.</P> </LI> <LI> <P>Select the Browse tab, search for the <STRONG>Microsoft.Windows.CppWinRT</STRONG> package, and install the latest version of this package.</P> </LI> </OL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="03.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167313i991706D6AD950497/image-size/large?v=v2&amp;px=999" role="button" title="03.png" alt="03.png" /></span></P> <P>After install the nuget package, check the SimpleApp project properties, you will notice its C++ version is ISO C++17, which is required by C++/WinRT:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="04.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167314iD58A11F7BAE3595C/image-size/large?v=v2&amp;px=999" role="button" title="04.png" alt="04.png" /></span></P> <P>Build this SimpleApp, we can see winrt projected files are generated in the “Generated Files” folder:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="05.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167315iED104A4EB6B54BB5/image-size/large?v=v2&amp;px=999" role="button" title="05.png" alt="05.png" /></span></P> </LI> <LI> <P>Install the <STRONG>Microsoft.Toolkit.Win32.UI.SDK</STRONG> NuGet package:</P> <OL type="a"> <LI>In the NuGet Package Manager window, make sure that Include prerelease is selected.</LI> <LI>Select the Browse tab, search for the <STRONG>Microsoft.Toolkit.Win32.UI.SDK</STRONG> package, and install version v6.0.0 (or Later) of this package.</LI> </OL> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="06.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167316i752FE572CB89AFF4/image-size/large?v=v2&amp;px=999" role="button" title="06.png" alt="06.png" /></span></P> </LI> <LI> <P>Install the <STRONG><EM>Microsoft.VCRTForwarders.140</EM></STRONG> nuget package as well. Running Custom UWP Control in this project will require VC libs.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="7.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167317i20E695EC5B8366BD/image-size/large?v=v2&amp;px=999" role="button" title="7.png" alt="7.png" /></span></P> </LI> </OL> <H2 id="configure-uwp-project">Configure UWP Project</H2> <OL type="1"> <LI> <P>In Solution Explorer, right-click the solution node and select Add -&gt; New Project.</P> </LI> <LI> <P>Add a Blank App (C++/WinRT) project to your solution.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167318i627F8757DB3FC2CB/image-size/medium?v=v2&amp;px=400" role="button" title="1.png" alt="1.png" /></span></P> Note: Please make sure this is C++/WinRT template. If cannot find it, you'll want to download and install the latest version of the&nbsp;<U style="font-family: inherit;"><A title="https://marketplace.visualstudio.com/items?itemname=cppwinrtteam.cppwinrt101804264" href="#" target="_blank" rel="noopener">C++/WinRT Visual Studio Extension (VSIX)</A></U><SPAN style="font-family: inherit;">&nbsp;from the&nbsp;</SPAN><U style="font-family: inherit;"><A title="https://marketplace.visualstudio.com/" href="#" target="_blank" rel="noopener">Visual Studio Marketplace</A></U><SPAN style="font-family: inherit;">.</SPAN></LI> <LI> <P>Give it a name <STRONG><EM>MyApp</EM></STRONG>, and create it, Make sure the target version and minimum version are <STRONG><EM>both</EM></STRONG> set to <STRONG><EM>Windows 10, version 1903</EM></STRONG> or later.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="8.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167319i3681969F9B416309/image-size/medium?v=v2&amp;px=400" role="button" title="8.png" alt="8.png" /></span></P> </LI> <LI> <P>Right click the MyApp and open its properties, make sure its C++/WinRT configuration is as below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="3.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167320iB90DDF093B187CB0/image-size/large?v=v2&amp;px=999" role="button" title="3.png" alt="3.png" /></span></P> </LI> <LI> <P>Change its output type from .EXE to <STRONG><EM>Dynamic Library</EM></STRONG></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="4.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167321i3C429D3D1389EBD8/image-size/large?v=v2&amp;px=999" role="button" title="4.png" alt="4.png" /></span></P> </LI> <LI> <P>Save a dummy.exe into the <STRONG><EM>MyApp</EM></STRONG> folder. It doesn’t need to be a real exe, just input “dummy exe file” in notepad, and save it as <STRONG><EM>dummy.exe</EM></STRONG>.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="5.png" style="width: 200px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167322iED0C9D3FA0203F98/image-size/small?v=v2&amp;px=200" role="button" title="5.png" alt="5.png" /></span></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="9.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167323i1F9D713AE26765B4/image-size/large?v=v2&amp;px=999" role="button" title="9.png" alt="9.png" /></span></P> <P>Add the dummy.exe into MyApp project, and make sure the Content property of dummy.exe is <STRONG><EM>True</EM></STRONG>.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="10.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167324i310F1081B603F9B2/image-size/medium?v=v2&amp;px=400" role="button" title="10.png" alt="10.png" /></span></P> </LI> <LI> <P>Now edit Package.appxmanifest, change the Executable attribute to “dummy.exe”</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="7.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167325iCB302692E43AD9F1/image-size/large?v=v2&amp;px=999" role="button" title="7.png" alt="7.png" /></span></P> </LI> <LI> <P>Right click the <STRONG><EM>MyApp</EM></STRONG> project, select <STRONG><EM>Unload Project</EM></STRONG></P> </LI> <LI> <P>Right click the <STRONG><EM>MyApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Edit MyApp.vcxproj</EM></STRONG></P> </LI> <LI> <P>Add below properties to the <STRONG><EM>MyApp.vcxproj</EM></STRONG> project file:</P> <DIV id="cb1" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;PropertyGroup</SPAN><SPAN class="ot"> Label=</SPAN><SPAN class="st">"Globals"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;WindowsAppContainer&gt;</SPAN>true<SPAN class="kw">&lt;/WindowsAppContainer&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppxGeneratePriEnabled&gt;</SPAN>true<SPAN class="kw">&lt;/AppxGeneratePriEnabled&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;ProjectPriIndexName&gt;</SPAN>App<SPAN class="kw">&lt;/ProjectPriIndexName&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppxPackage&gt;</SPAN>true<SPAN class="kw">&lt;/AppxPackage&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P>For example:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="8.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167326i82E993B58B384109/image-size/large?v=v2&amp;px=999" role="button" title="8.png" alt="8.png" /></span></P> </LI> <LI> <P>Right click the <STRONG><EM>MyApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Reload Project</EM></STRONG>.</P> </LI> <LI> <P>Right click <STRONG><EM>Mainpage.xml</EM></STRONG>, select <STRONG><EM>Remove</EM></STRONG>. And then click <STRONG><EM>Delete</EM></STRONG></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="11.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167327i35F8FF2A3804B682/image-size/medium?v=v2&amp;px=400" role="button" title="11.png" alt="11.png" /></span></P> </LI> <LI> <P>Copy App.Xaml, App.cpp, App.h, App.idl contents to overwrite current ones:</P> <P><STRONG><EM>App.Xaml</EM></STRONG></P> <DIV id="cb2" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;Toolkit:XamlApplication</SPAN></SPAN> <SPAN><SPAN class="ot"> x:Class=</SPAN><SPAN class="st">"MyApp.App"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:x=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:local=</SPAN><SPAN class="st">"using:MyApp"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:Toolkit=</SPAN><SPAN class="st">"using:Microsoft.Toolkit.Win32.UI.XamlHost"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:d=</SPAN><SPAN class="st">"http://schemas.microsoft.com/expression/blend/2008"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:mc=</SPAN><SPAN class="st">"http://schemas.openxmlformats.org/markup-compatibility/2006"</SPAN></SPAN> <SPAN><SPAN class="ot"> RequestedTheme=</SPAN><SPAN class="st">"Light"</SPAN></SPAN> <SPAN><SPAN class="ot"> mc:Ignorable=</SPAN><SPAN class="st">"d"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/Toolkit:XamlApplication&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P><STRONG><EM>App.cpp</EM></STRONG></P> <DIV id="cb3" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"pch.h"</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"App.h"</SPAN></SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> winrt;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Xaml;</SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::implementation</SPAN> <SPAN>{</SPAN> <SPAN> App::App()</SPAN> <SPAN> {</SPAN> <SPAN> Initialize();</SPAN> <SPAN> AddRef();</SPAN> <SPAN> <SPAN class="va">m_inner</SPAN>.as&lt;::IUnknown&gt;()-&gt;Release();</SPAN> <SPAN> }</SPAN> <SPAN> App::~App()</SPAN> <SPAN> {</SPAN> <SPAN> Close();</SPAN> <SPAN> }</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> <P><STRONG><EM>App.h</EM></STRONG></P> <DIV id="cb4" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="co">//</SPAN></SPAN> <SPAN><SPAN class="co">// Declaration of the App class.</SPAN></SPAN> <SPAN><SPAN class="co">//</SPAN></SPAN> <SPAN><SPAN class="pp">#pragma once</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"App.g.h"</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">"App.base.h"</SPAN></SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::implementation</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="kw">class</SPAN> App : <SPAN class="kw">public</SPAN> AppT2&lt;App&gt;</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="kw">public</SPAN>:</SPAN> <SPAN> App();</SPAN> <SPAN> ~App();</SPAN> <SPAN> };</SPAN> <SPAN>}</SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::factory_implementation</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="kw">class</SPAN> App : <SPAN class="kw">public</SPAN> AppT&lt;App, implementation::App&gt;</SPAN> <SPAN> {</SPAN> <SPAN> };</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> <P><STRONG><EM>App.idl</EM></STRONG></P> <DIV id="cb5" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="kw">namespace</SPAN> MyApp</SPAN> <SPAN>{</SPAN> <SPAN> [default_interface]</SPAN> <SPAN> runtimeclass App: Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication</SPAN> <SPAN> {</SPAN> <SPAN> App();</SPAN> <SPAN> }</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Create app.base.h in this MyApp project, and use below content:</P> <P><STRONG><EM>app.base.h</EM></STRONG></P> <DIV id="cb6" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#pragma once</SPAN></SPAN> <SPAN><SPAN class="kw">namespace</SPAN> winrt::MyApp::implementation</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="kw">template</SPAN> &lt;<SPAN class="kw">typename</SPAN> D, <SPAN class="kw">typename</SPAN>... I&gt;</SPAN> <SPAN> <SPAN class="kw">struct</SPAN> App_baseWithProvider : <SPAN class="kw">public</SPAN> App_base&lt;D, ::winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider&gt;</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="kw">using</SPAN> IXamlType = ::winrt::Windows::UI::Xaml::Markup::IXamlType;</SPAN> <SPAN> IXamlType GetXamlType(::winrt::Windows::UI::Xaml::Interop::TypeName <SPAN class="at">const</SPAN>&amp; type)</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">return</SPAN> AppProvider()-&gt;GetXamlType(type);</SPAN> <SPAN> }</SPAN> <SPAN> IXamlType GetXamlType(::winrt::hstring <SPAN class="at">const</SPAN>&amp; fullName)</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">return</SPAN> AppProvider()-&gt;GetXamlType(fullName);</SPAN> <SPAN> }</SPAN> <SPAN> ::winrt::com_array&lt;::winrt::Windows::UI::Xaml::Markup::XmlnsDefinition&gt; GetXmlnsDefinitions()</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">return</SPAN> AppProvider()-&gt;GetXmlnsDefinitions();</SPAN> <SPAN> }</SPAN> <SPAN> <SPAN class="kw">private</SPAN>:</SPAN> <SPAN> <SPAN class="dt">bool</SPAN> _contentLoaded{ <SPAN class="kw">false</SPAN> };</SPAN> <SPAN> <SPAN class="bu">std::</SPAN>shared_ptr&lt;XamlMetaDataProvider&gt; _appProvider;</SPAN> <SPAN> <SPAN class="bu">std::</SPAN>shared_ptr&lt;XamlMetaDataProvider&gt; AppProvider()</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="cf">if</SPAN> (!_appProvider)</SPAN> <SPAN> {</SPAN> <SPAN> _appProvider = <SPAN class="bu">std::</SPAN>make_shared&lt;XamlMetaDataProvider&gt;();</SPAN> <SPAN> }</SPAN> <SPAN> <SPAN class="cf">return</SPAN> _appProvider;</SPAN> <SPAN> }</SPAN> <SPAN> };</SPAN> <SPAN> <SPAN class="kw">template</SPAN> &lt;<SPAN class="kw">typename</SPAN> D, <SPAN class="kw">typename</SPAN>... I&gt;</SPAN> <SPAN> <SPAN class="kw">using</SPAN> AppT2 = App_baseWithProvider&lt;D, I...&gt;;</SPAN> <SPAN>}</SPAN> </CODE></PRE> </DIV> </LI> <LI> <P>Install <A href="#" target="_blank" rel="noopener">Microsoft.Toolkit.Win32.UI.XamlApplication</A> Nuget package.</P> </LI> <LI> <P>If you build <STRONG><EM>MyApp</EM></STRONG> now, it should create MyApp.dll without any error.</P> </LI> </OL> <H2 id="centralize-output-input-and-cwinrt-files-in-solution">Centralize Output, Input and C++/WinRT files in Solution</H2> <P>This step is necessary for our next steps because we need to include winrt header files in different projects properly, and SimpleApp also needs to reference MyApp resource files.</P> <OL type="1"> <LI> <P>Add a new <STRONG><EM>Solution.Props</EM></STRONG> file by right clicking the <STRONG><EM>solution</EM></STRONG> node, and select Add -&gt; New Item:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="13.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167328i9559155B22C64DF3/image-size/large?v=v2&amp;px=999" role="button" title="13.png" alt="13.png" /></span></P> </LI> <LI> <P>Use below content to overwrite the Solution.Props:</P> <DIV id="cb7" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;?xml</SPAN> version="1.0" encoding="utf-8"<SPAN class="kw">?&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;Project</SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"http://schemas.microsoft.com/developer/msbuild/2003"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;IntDir&gt;</SPAN>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\<SPAN class="kw">&lt;/IntDir&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;OutDir&gt;</SPAN>$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(MSBuildProjectName)\<SPAN class="kw">&lt;/OutDir&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;GeneratedFilesDir&gt;</SPAN>$(IntDir)Generated Files\<SPAN class="kw">&lt;/GeneratedFilesDir&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/Project&gt;</SPAN></SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Click Views -&gt; Other Windows -&gt; Property Manager</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="12.png" style="width: 200px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167329i856685A95450AF93/image-size/small?v=v2&amp;px=200" role="button" title="12.png" alt="12.png" /></span></P> </LI> <LI> <P>Right click <STRONG><EM>SimpleApp</EM></STRONG>, select <STRONG><EM>Add Existing Property Sheet</EM></STRONG>, add the new <STRONG><EM>solution.props</EM></STRONG> file</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="13.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167330i4D68B095F20CDFE4/image-size/medium?v=v2&amp;px=400" role="button" title="13.png" alt="13.png" /></span></P> </LI> <LI> <P>Repeat the step 4. for <STRONG><EM>MyApp</EM></STRONG>. We can close the Property Manager window now.</P> </LI> <LI> <P>Right click the project node <STRONG><EM>My<STRONG>A</STRONG>pp</EM></STRONG>, select Properties.</P> <P>If you see the Outut and Intermidiate directries are already become as below, then can skip this step:</P> <P><CODE class="sourceCode xml"><SPAN>$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</SPAN></CODE><BR />and <CODE class="sourceCode xml"><SPAN>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</SPAN></CODE></P> <P>Otherwise please manually set:</P> <P>Output Directory: $(OutDir)</P> <P>Intermidia Directory: $(IntDir)</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="12.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167331i6CABD54FCFDC00E3/image-size/large?v=v2&amp;px=999" role="button" title="12.png" alt="12.png" /></span></P> </LI> <LI> <P>Repeat the step 6 for <STRONG><EM>MyApp</EM></STRONG>.</P> </LI> <LI> <P>Right click the Solution node, and choose <STRONG><EM>Project Dependencies</EM></STRONG>, make sure SimpleApp depends on MyApp:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="15.png" style="width: 357px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167332i9D195788C147016B/image-size/medium?v=v2&amp;px=400" role="button" title="15.png" alt="15.png" /></span></P> </LI> <LI> <P>Rebuild the whole solution, it should work without errors.</P> </LI> </OL> <H2 id="add-uwp-custom-xaml-control-to-myapp">Add UWP Custom XAML Control to MyApp</H2> <OL type="1"> <LI> <P>Right click <STRONG><EM>MyApp</EM></STRONG>, select Add -&gt; New Item</P> </LI> <LI> <P>Create <STRONG><EM>Blank User Conrol (C++/WinRT)</EM></STRONG>, here we call it MainUserControl:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="16.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167333i9DA901C8378FEB50/image-size/large?v=v2&amp;px=999" role="button" title="16.png" alt="16.png" /></span></P> </LI> </OL> <H2 id="integrate-custom-xaml-control-in-simpleapp">Integrate Custom XAML Control in SimpleApp</H2> <OL type="1"> <LI> <P>Add one XML file <STRONG>app.manifest</STRONG> in your project with below content to register custom control type:</P> <DIV id="cb8" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;?xml</SPAN> version="1.0" encoding="utf-8" standalone="yes"<SPAN class="kw">?&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;assembly</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:asm.v1"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:asmv3=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:asm.v3"</SPAN></SPAN> <SPAN><SPAN class="ot"> manifestVersion=</SPAN><SPAN class="st">"1.0"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;asmv3:file</SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.dll"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;activatableClass</SPAN></SPAN> <SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.App"</SPAN></SPAN> <SPAN><SPAN class="ot"> threadingModel=</SPAN><SPAN class="st">"both"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:winrt.v1"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;activatableClass</SPAN></SPAN> <SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.XamlMetadataProvider"</SPAN></SPAN> <SPAN><SPAN class="ot"> threadingModel=</SPAN><SPAN class="st">"both"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:winrt.v1"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;activatableClass</SPAN></SPAN> <SPAN><SPAN class="ot"> name=</SPAN><SPAN class="st">"MyApp.MainUserControl"</SPAN></SPAN> <SPAN><SPAN class="ot"> threadingModel=</SPAN><SPAN class="st">"both"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"urn:schemas-microsoft-com:winrt.v1"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/asmv3:file&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/assembly&gt;</SPAN></SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Now the project structure is like as below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="17.png" style="width: 200px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167334i4032DBB55E9D5A79/image-size/small?v=v2&amp;px=200" role="button" title="17.png" alt="17.png" /></span></P> </LI> <LI> <P>Right click the Win32 Project <STRONG><EM>SimpleApp</EM></STRONG>, select <STRONG><EM>Unload Project</EM></STRONG></P> </LI> <LI> <P>Right click the <STRONG><EM>SimpleApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Edit SimpleApp.vcxproj</EM></STRONG></P> </LI> <LI> <P>Remove the three lines from the <STRONG><EM>SimpleApp.vcxproj</EM></STRONG> project file:</P> <DIV id="cb9" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;Manifest</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"app.manifest"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/ItemGroup&gt;</SPAN></SPAN></CODE></PRE> </DIV> <P>Add below properties to the <STRONG><EM>SimpleApp.vcxproj</EM></STRONG> project file before the <STRONG><EM>“&lt;Import Project=”“$(VCTargetsPath).Cpp.targets” /&gt;"</EM></STRONG> line:</P> <DIV id="cb10" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN> <SPAN class="kw">&lt;ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;Manifest</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"app.manifest"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppxManifest</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\AppxManifest.xml"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppProjectName&gt;</SPAN>MyApp<SPAN class="kw">&lt;/AppProjectName&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;AppIncludeDirectories&gt;</SPAN>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(AppProjectName)\;$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(AppProjectName)\Generated Files\;<SPAN class="kw">&lt;/AppIncludeDirectories&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/PropertyGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;NativeReferenceFile</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\*.xbf"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;DeploymentContent&gt;</SPAN>true<SPAN class="kw">&lt;/DeploymentContent&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;CopyToOutputDirectory&gt;</SPAN>PreserveNewest<SPAN class="kw">&lt;/CopyToOutputDirectory&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/NativeReferenceFile&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;NativeReferenceFile</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\*.dll"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;DeploymentContent&gt;</SPAN>true<SPAN class="kw">&lt;/DeploymentContent&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;CopyToOutputDirectory&gt;</SPAN>PreserveNewest<SPAN class="kw">&lt;/CopyToOutputDirectory&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/NativeReferenceFile&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;NativeReferenceFile</SPAN><SPAN class="ot"> Include=</SPAN><SPAN class="st">"$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(AppProjectName)\resources.pri"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;DeploymentContent&gt;</SPAN>true<SPAN class="kw">&lt;/DeploymentContent&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;CopyToOutputDirectory&gt;</SPAN>PreserveNewest<SPAN class="kw">&lt;/CopyToOutputDirectory&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/NativeReferenceFile&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/ItemGroup&gt;</SPAN></SPAN> <SPAN> <SPAN class="er">&lt;</SPAN>------Right Here---------&gt;</SPAN> <SPAN> <SPAN class="kw">&lt;Import</SPAN><SPAN class="ot"> Project=</SPAN><SPAN class="st">"$(VCTargetsPath)\Microsoft.Cpp.targets"</SPAN> <SPAN class="kw">/&gt;</SPAN></SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Right click the <STRONG><EM>SimpleApp (Unloaded)</EM></STRONG> project, select <STRONG><EM>Reload Project</EM></STRONG>.</P> </LI> <LI> <P>Right click <STRONG><EM>SimpleApp</EM></STRONG>, select <STRONG><EM>Properties</EM></STRONG>, setup <STRONG><EM>$(AppIncludeDirectories)</EM></STRONG> as a part of include file path, this macro has been defined in the above project file:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="18.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167335i3D2A547AD082D4BB/image-size/large?v=v2&amp;px=999" role="button" title="18.png" alt="18.png" /></span></P> </LI> <LI> <P>Set <STRONG><EM>“Per Monitor DPI Aware”</EM></STRONG> for <STRONG><EM>DPI Awareness</EM></STRONG> otherwise you may be not able to start this SimpleApp when it is <STRONG><EM>“High DPI Aware”</EM></STRONG> and hit configuration error in Manifest:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="19.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167336i342C4977A08A4745/image-size/medium?v=v2&amp;px=400" role="button" title="19.png" alt="19.png" /></span></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="20.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167337i4A85CE310D203A6A/image-size/large?v=v2&amp;px=999" role="button" title="20.png" alt="20.png" /></span></P> </LI> <LI> <P>Open framework.h, remove this #define:</P> <DIV id="cb11" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#define WIN32_LEAN_AND_MEAN</SPAN></SPAN></CODE></PRE> </DIV> </LI> <LI> <P>In SimpleApp.h, add below code to include necessary winrt header files:</P> <DIV id="cb12" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.Foundation.Collections.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.system.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/windows.ui.xaml.hosting.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;windows.ui.xaml.hosting.desktopwindowxamlsource.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/windows.ui.xaml.controls.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.ui.xaml.media.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.UI.Core.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/myapp.h&gt;</SPAN></SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Using winrt namespaces in SimpleAPP.cpp</P> <DIV id="cb13" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> winrt;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Composition;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Xaml::Hosting;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::Foundation::Numerics;</SPAN> <SPAN><SPAN class="kw">using</SPAN> <SPAN class="kw">namespace</SPAN> Windows::UI::Xaml::Controls;</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Declare hostApp, _desktopWindowXamlSource and our custom control in <STRONG><EM>SimpleApp.cpp</EM></STRONG></P> <DIV id="cb14" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN> <SPAN class="co">// Global Variables:</SPAN></SPAN> <SPAN> HINSTANCE hInst; </SPAN> <SPAN> <SPAN class="co">// current instance</SPAN></SPAN> <SPAN> WCHAR szTitle[MAX_LOADSTRING]; </SPAN> <SPAN> <SPAN class="co">// The title bar text</SPAN></SPAN> <SPAN> WCHAR szWindowClass[MAX_LOADSTRING]; </SPAN> <SPAN> <SPAN class="co">// the main window class name</SPAN></SPAN> <SPAN> winrt::MyApp::App hostApp{ <SPAN class="kw">nullptr</SPAN> };</SPAN> <SPAN> winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _desktopWindowXamlSource{ <SPAN class="kw">nullptr</SPAN> };</SPAN> <SPAN> winrt::MyApp::MainUserControl _mainUserControl{ <SPAN class="kw">nullptr</SPAN> };</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Initalize hostApp, _desktopWindowXamlSource in <STRONG><EM>wWinMain</EM></STRONG> in <STRONG><EM>SimpleApp.CPP</EM></STRONG></P> <DIV id="cb15" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN> winrt::init_apartment(winrt::<SPAN class="dt">apartment_type</SPAN>::single_threaded);</SPAN> <SPAN> hostApp = winrt::MyApp::App{};</SPAN> <SPAN> _desktopWindowXamlSource = winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource{};</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Add below code in <STRONG><EM>InitInstance</EM></STRONG> in <STRONG><EM>SimpleApp.cpp</EM></STRONG></P> <DIV id="cb16" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN> <SPAN class="cf">if</SPAN> (_desktopWindowXamlSource != <SPAN class="kw">nullptr</SPAN>)</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="co">// Get handle to corewindow</SPAN></SPAN> <SPAN> <SPAN class="kw">auto</SPAN> interop = _desktopWindowXamlSource.as&lt;IDesktopWindowXamlSourceNative&gt;();</SPAN> <SPAN> <SPAN class="co">// Parent the DesktopWindowXamlSource object to current window</SPAN></SPAN> <SPAN> check_hresult(interop-&gt;AttachToWindow(hWnd));</SPAN> <SPAN> <SPAN class="co">// This Hwnd will be the window handler for the Xaml Island: A child window that contains Xaml. </SPAN></SPAN> <SPAN> HWND hWndXamlIsland = <SPAN class="kw">nullptr</SPAN>;</SPAN> <SPAN> <SPAN class="co">// Get the new child window's hwnd </SPAN></SPAN> <SPAN> interop-&gt;get_WindowHandle(&amp;hWndXamlIsland);</SPAN> <SPAN> RECT windowRect;</SPAN> <SPAN> ::GetWindowRect(hWnd, &amp;windowRect);</SPAN> <SPAN> ::SetWindowPos(hWndXamlIsland, NULL, <SPAN class="dv">0</SPAN>, <SPAN class="dv">0</SPAN>, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_SHOWWINDOW);</SPAN> <SPAN> _mainUserControl = winrt::MyApp::MainUserControl();</SPAN> <SPAN> _desktopWindowXamlSource.Content(_mainUserControl);</SPAN> <SPAN> }</SPAN> <SPAN> ShowWindow(hWnd, nCmdShow);</SPAN> <SPAN> UpdateWindow(hWnd);</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Clean up resources when the view is disconstructed in SimpleAppView.cpp</P> <DIV id="cb17" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN> <SPAN class="cf">case</SPAN> WM_DESTROY:</SPAN> <SPAN> PostQuitMessage(<SPAN class="dv">0</SPAN>);</SPAN> <SPAN> <SPAN class="cf">if</SPAN> (_desktopWindowXamlSource != <SPAN class="kw">nullptr</SPAN>)</SPAN> <SPAN> {</SPAN> <SPAN> _desktopWindowXamlSource.Close();</SPAN> <SPAN> _desktopWindowXamlSource = <SPAN class="kw">nullptr</SPAN>;</SPAN> <SPAN> }</SPAN> <SPAN> <SPAN class="cf">break</SPAN>;</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Add AdjustLayout function to make XAML content layout properly in SimpleApp.cpp :</P> <DIV id="cb18" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="dt">void</SPAN> AdjustLayout(HWND hWnd)</SPAN> <SPAN>{</SPAN> <SPAN> <SPAN class="cf">if</SPAN> (_desktopWindowXamlSource != <SPAN class="kw">nullptr</SPAN>)</SPAN> <SPAN> {</SPAN> <SPAN> <SPAN class="kw">auto</SPAN> interop = _desktopWindowXamlSource.as&lt;IDesktopWindowXamlSourceNative&gt;();</SPAN> <SPAN> HWND xamlHostHwnd = NULL;</SPAN> <SPAN> check_hresult(interop-&gt;get_WindowHandle(&amp;xamlHostHwnd));</SPAN> <SPAN> RECT windowRect;</SPAN> <SPAN> ::GetWindowRect(hWnd, &amp;windowRect);</SPAN> <SPAN> ::SetWindowPos(xamlHostHwnd, NULL, <SPAN class="dv">0</SPAN>, <SPAN class="dv">0</SPAN>, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_SHOWWINDOW);</SPAN> <SPAN> }</SPAN> <SPAN>}</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>In SimpleApp.CPP handle WM_SIZE in the <STRONG><EM>WinProc</EM></STRONG> function: <CODE>C++ case WM_SIZE: AdjustLayout(hWnd);</CODE></P> </LI> <LI> <P>Now you can build and run this SimpleApp. It should display a button in the central of view window:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="21.gif" style="width: 856px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167338i5859E356479D4422/image-size/large?v=v2&amp;px=999" role="button" title="21.gif" alt="21.gif" /></span></P> </LI> </OL> <H2 id="using-ink-control-in-myapp-uwp-project">Using Ink Control in MyApp UWP Project</H2> <OL type="1"> <LI> <P>Add an <STRONG><EM>Assets</EM></STRONG> file folder under <STRONG><EM>SimpleApp</EM></STRONG> on file system, add viewbackground.png file. And then create a new filter of “Assets”, add this existing viewbackground.png into the <STRONG><EM>Assets</EM></STRONG> filter:</P> <P>Set the file’s <STRONG>Content</STRONG> property as <STRONG>True</STRONG></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="22.png" style="width: 989px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167339iB1EE728F2DA1708D/image-size/large?v=v2&amp;px=999" role="button" title="22.png" alt="22.png" /></span></P> <P>If you cannot see the <STRONG><EM>viewbackground.png</EM></STRONG> file, please click the “Show All Files” tool icon in the solution explorer window.</P> </LI> <LI> <P>Add the sample png file into <STRONG><EM>Assets</EM></STRONG> folder of <STRONG><EM>MyApp</EM></STRONG>, and make its <STRONG><EM>Content</EM></STRONG> property as <STRONG>True</STRONG> as well.</P> </LI> <LI> <P>Modify MainUserControl.Xaml as below in <STRONG><EM>MyApp</EM></STRONG>:</P> <DIV id="cb19" class="sourceCode"> <PRE class="sourceCode xml"><CODE class="sourceCode xml"><SPAN><SPAN class="kw">&lt;UserControl</SPAN></SPAN> <SPAN><SPAN class="ot"> x:Class=</SPAN><SPAN class="st">"MyApp.MainUserControl"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:x=</SPAN><SPAN class="st">"http://schemas.microsoft.com/winfx/2006/xaml"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:local=</SPAN><SPAN class="st">"using:MyApp"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:d=</SPAN><SPAN class="st">"http://schemas.microsoft.com/expression/blend/2008"</SPAN></SPAN> <SPAN><SPAN class="ot"> xmlns:mc=</SPAN><SPAN class="st">"http://schemas.openxmlformats.org/markup-compatibility/2006"</SPAN></SPAN> <SPAN><SPAN class="ot"> mc:Ignorable=</SPAN><SPAN class="st">"d"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;RelativePanel</SPAN><SPAN class="ot"> HorizontalAlignment=</SPAN><SPAN class="st">"Stretch"</SPAN><SPAN class="ot"> VerticalAlignment=</SPAN><SPAN class="st">"Stretch"</SPAN><SPAN class="kw">&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;Image</SPAN><SPAN class="ot"> Source=</SPAN><SPAN class="st">"assets/viewbackground.png"</SPAN><SPAN class="ot"> RelativePanel.AlignLeftWithPanel=</SPAN><SPAN class="st">"True"</SPAN> </SPAN> <SPAN><SPAN class="ot"> RelativePanel.AlignRightWithPanel=</SPAN><SPAN class="st">"True"</SPAN></SPAN> <SPAN><SPAN class="ot"> RelativePanel.AlignHorizontalCenterWithPanel=</SPAN><SPAN class="st">"True"</SPAN><SPAN class="kw">&gt;&lt;/Image&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;InkCanvas</SPAN><SPAN class="ot"> x:Name=</SPAN><SPAN class="st">"ic"</SPAN><SPAN class="ot"> RelativePanel.AlignLeftWithPanel=</SPAN><SPAN class="st">"True"</SPAN> </SPAN> <SPAN><SPAN class="ot"> RelativePanel.AlignRightWithPanel=</SPAN><SPAN class="st">"True"</SPAN> </SPAN> <SPAN><SPAN class="ot"> RelativePanel.AlignBottomWithPanel=</SPAN><SPAN class="st">"True"</SPAN></SPAN> <SPAN><SPAN class="ot"> RelativePanel.AlignTopWithPanel=</SPAN><SPAN class="st">"True"</SPAN><SPAN class="kw">&gt;&lt;/InkCanvas&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;InkToolbar</SPAN><SPAN class="ot"> x:Name=</SPAN><SPAN class="st">"it"</SPAN><SPAN class="ot"> HorizontalAlignment=</SPAN><SPAN class="st">"Left"</SPAN><SPAN class="ot"> VerticalAlignment=</SPAN><SPAN class="st">"Top"</SPAN><SPAN class="kw">&gt;&lt;/InkToolbar&gt;</SPAN></SPAN> <SPAN> <SPAN class="kw">&lt;/RelativePanel&gt;</SPAN></SPAN> <SPAN><SPAN class="kw">&lt;/UserControl&gt;</SPAN></SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Add below code in MainUserControl.CPP, remove the Button ClickHandler code as we don’t use it now:</P> <DIV id="cb20" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN> MainUserControl::MainUserControl()</SPAN> <SPAN> {</SPAN> <SPAN> InitializeComponent();</SPAN> <SPAN> ic().InkPresenter().InputDeviceTypes((winrt::Windows::UI::Core::CoreInputDeviceTypes)<SPAN class="dv">7</SPAN>);</SPAN> <SPAN> it().TargetInkCanvas(ic());</SPAN> <SPAN> }</SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Include more winrt header files in MainUserControl.h:</P> <DIV id="cb21" class="sourceCode"> <PRE class="sourceCode c++"><CODE class="sourceCode cpp"><SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.UI.Input.Inking.h&gt;</SPAN></SPAN> <SPAN><SPAN class="pp">#include </SPAN><SPAN class="im">&lt;winrt/Windows.UI.Xaml.Media.Imaging.h&gt;</SPAN></SPAN></CODE></PRE> </DIV> </LI> <LI> <P>Build and run Simple, if steps have been taken exactly as above, it will show as below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="23.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167340i339E79FDCDDCEF04/image-size/large?v=v2&amp;px=999" role="button" title="23.png" alt="23.png" /></span></P> </LI> <LI> <P>You may notice that it doesn’t display the background image, this is because the uri <STRONG>assets/viewbackground.png</STRONG> needs to be used UWP package. In Visual Studio, with <STRONG>“Windows Application Packaging Project (C++)”</STRONG>, it is easily to packaging our SimpleApp project:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="16.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167341i71A911326DB368E5/image-size/large?v=v2&amp;px=999" role="button" title="16.png" alt="16.png" /></span></P> <P>Create the packaging project in the solution, right click the <STRONG>Application</STRONG> node, and select <STRONG>Add Reference</STRONG>, add SimpleApp. Now the packaging project structure is like:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="24.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167342i13D3D667EE411FA9/image-size/medium?v=v2&amp;px=400" role="button" title="24.png" alt="24.png" /></span></P> <P>For more information about packaging project, refer to: <A href="#" target="_blank" rel="noopener">Package a desktop app from source code using Visual Studio</A></P> </LI> <LI> <P>After this, choose the packaging project as Start Up project, Ctrl+F5 to run it. We can see the expected result will show up:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="25.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167343iB872CEBBB6F0EF02/image-size/large?v=v2&amp;px=999" role="button" title="25.png" alt="25.png" /></span></P> <P>Further more, you can publish this packaging app as MSIX or APPX, and easily deploy it:</P> <P><A href="#" target="_blank" rel="noopener">Package a UWP app with Visual Studio</A></P> </LI> </OL> <H2 id="wrap-up">Wrap Up</H2> <P>This article gives detailed steps on how to leverage XamplApplication to host standard XAML control in C++ Win32 project, with this method, it is flexible to leverage Win10 Controls in Win32 project. The whole sample solution can be found from this repo: <A href="#" target="_blank" rel="noopener">https://github.com/freistli/ModernizeApp/tree/master/C%2B%2B/SimpleApp</A></P> <H2 id="further-step">Further Step</H2> <P>To use WinUI 2.x, you can refer to this <A href="#" target="_blank" rel="noopener">part</A> and embedded it in your C++ project. The sample solution is also ready from this repo: <A href="#" target="_blank" rel="noopener">https://github.com/freistli/ModernizeApp/tree/master/C%2B%2B/SimpleAppWithWinUI/SimpleApp</A>. Its left TreeView is from WinUI 2.4 prerelease:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="26.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/167344iA94CDECBF87F3DA5/image-size/large?v=v2&amp;px=999" role="button" title="26.png" alt="26.png" /></span></P> Mon, 07 Sep 2020 14:14:53 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/host-custom-uwp-controls-in-c-win32-project-using-xaml-islands/ba-p/1129823 freistli 2020-09-07T14:14:53Z Supporting a modular Windows application with MSIX and Optional Packages https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/supporting-a-modular-windows-application-with-msix-and-optional/ba-p/1124865 <P>MSIX aims to become the best packaging and deployment technology for Windows desktop applications and, on this blog, we have seen multiple times its advantages: the clean install &amp; uninstall experience, the network bandwidth and disk space optimizations, the support to automatic updates even without using the Microsoft Store, etc. However, there is a category of applications for which, at first glance, adopting MSIX could be a challenge: modular applications. We're talking about applications that offers a set of core features, but that can be expanded with additional ones by installing additional components, like plug-ins or modules. There are many applications on the market that behave this way: Paint.NET, Office, etc.</P> <P>&nbsp;</P> <P>The challenge comes from the fact that Win32 applications packaged with MSIX run inside a lightweight container. It isn't a full sandbox like the one leveraged by Universal Windows Platform apps, but still helps to isolate some of the most critical aspects of an application, like the registry or the AppData folder in the file system. If this feature bring many security and reliability benefits, it can be a challenge for applications that need to be extended with additional modules. Since the registry and the file system are isolated, for a module it can be hard to interact with the main application.</P> <P>&nbsp;</P> <P>In this blog post we're going to explore a couple of options to implement this scenario with MSIX. But first, let's introduce the application we're going to use to demonstrate the scenario.</P> <P>&nbsp;</P> <H3 id="an-extensible-application">An extensible application</H3> <P>The starting point is a Windows Forms application built on top of .NET Framework 4.8, which is available on<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">GitHub</A>. The application, called MyEmployees, is very simple: it displays a list of employees that is stored in a SQLite database which ships with the application itself. The data access layer is implemented using the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">System.Data.SQLite</A><SPAN>&nbsp;</SPAN>library, which is an ADO.NET provider for SQLite. The frontend, instead, is made by a form which includes a<SPAN>&nbsp;</SPAN><CODE>DataGrid</CODE><SPAN>&nbsp;</SPAN>control, which is populated by a<SPAN>&nbsp;</SPAN><CODE>BindingSource</CODE>. When the application starts, we execute a query on the SQLite database to retrieve all the employees. I won't go into the details in this post because it would be out of scope from the purpose of the article. The key feature we're interested to implement is extensions support. The application allows to export the list of employees in a CSV file. However, this feature isn't implemented directly in the main core, but in a separate plugin that can be installed independently. If this plugin is found, the application will enable the export option; otherwise, it will stay hidden.</P> <P>&nbsp;</P> <P>Let's see, step by step, how it has been implemented.</P> <P>&nbsp;</P> <H4 id="define-a-common-interface">Define a common interface</H4> <P>As first step, we need to define a generic interface that describes a plugin. This interface must be implemented by every plugin and the main application must be aware of it. Every plugin, in fact, can offer different features, but it must provide a standard way to be leveraged by the main application. Since the interface must be shared between every plugin and the main application, we're going to create a dedicated Class Library to store it.</P> <P>&nbsp;</P> <P>This is the purpose of the<SPAN>&nbsp;</SPAN><STRONG>MyEmployees.PluginInterface</STRONG><SPAN>&nbsp;</SPAN>project you can see in the Visual Studio solution. It contains just a single file, which defines a generic<SPAN>&nbsp;</SPAN><CODE>IPlugin</CODE><SPAN>&nbsp;</SPAN>interface:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-23" class="language-csharp hljs"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">interface</SPAN> <SPAN class="hljs-title">IPlugin</SPAN> { <SPAN class="hljs-keyword">string</SPAN> Name { <SPAN class="hljs-keyword">get</SPAN>; } <SPAN class="hljs-function"><SPAN class="hljs-keyword">string</SPAN> <SPAN class="hljs-title">GetDescription</SPAN>()</SPAN>; <SPAN class="hljs-function"><SPAN class="hljs-keyword">bool</SPAN> <SPAN class="hljs-title">Execute</SPAN>(<SPAN class="hljs-params">IList data, <SPAN class="hljs-keyword">string</SPAN> filePath</SPAN>)</SPAN>; <SPAN class="hljs-keyword">event</SPAN> EventHandler OnExecute; } </CODE></PRE> <P>The<SPAN>&nbsp;</SPAN><CODE>Execute()</CODE><SPAN>&nbsp;</SPAN>method is the one which we're going to use to implement the export logic. It receives, as parameters from the main application, the list of employees and the path where to save the CSV file.</P> <P>&nbsp;</P> <H4 id="implement-the-plugin">Implement the plugin</H4> <P>Now that we have an interface, we can implement it. However, since the plugin is an extension, we're going to do it in another class library. It's the project called<SPAN>&nbsp;</SPAN><STRONG>ExportDataLibrary</STRONG>. Let's take a look at the<SPAN>&nbsp;</SPAN><STRONG>ExportData.cs</STRONG><SPAN>&nbsp;</SPAN>file included in the project:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-41" class="language-csharp hljs"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">ExportData</SPAN>: <SPAN class="hljs-title">IPlugin</SPAN> { <SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">string</SPAN> Name =&gt; <SPAN class="hljs-string">"Export Data"</SPAN>; <SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">event</SPAN> EventHandler OnExecute; <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">bool</SPAN> <SPAN class="hljs-title">Execute</SPAN>(<SPAN class="hljs-params">IList data, <SPAN class="hljs-keyword">string</SPAN> filePath</SPAN>)</SPAN> { <SPAN class="hljs-keyword">try</SPAN> { <SPAN class="hljs-keyword">using</SPAN> (TextWriter textWriter = File.CreateText(filePath)) { CsvWriter csvWriter = <SPAN class="hljs-keyword">new</SPAN> CsvWriter(textWriter); csvWriter.Configuration.Delimiter = <SPAN class="hljs-string">";"</SPAN>; csvWriter.WriteRecords(data); } <SPAN class="hljs-keyword">return</SPAN> <SPAN class="hljs-literal">true</SPAN>; } <SPAN class="hljs-keyword">catch</SPAN> (Exception exc) { <SPAN class="hljs-keyword">return</SPAN> <SPAN class="hljs-literal">false</SPAN>; } } <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">string</SPAN> <SPAN class="hljs-title">GetDescription</SPAN>()</SPAN> { <SPAN class="hljs-keyword">return</SPAN> <SPAN class="hljs-string">"Export data to CSV"</SPAN>; } }</CODE></PRE> <P>&nbsp;</P> <P><SPAN>We're implementing every property and method exposed by the&nbsp;</SPAN><CODE>IPlugin</CODE><SPAN>&nbsp;interface. The core of the plugin is the&nbsp;</SPAN><CODE>Execute()</CODE><SPAN>&nbsp;method, which contains the logic to export the CSV file. The task is achieved using a library called&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">CsvHelper</A><SPAN>, which makes the whole implementation really easy. We create a&nbsp;</SPAN><CODE>TextWriter</CODE><SPAN>&nbsp;object, which is linked to the file path passed by the main application. We use it to initialize a&nbsp;</SPAN><CODE>CsvWriter</CODE><SPAN>&nbsp;object. We setup the delimiter to use (a semi colon) and then we write the list of employees passed by the main application using the&nbsp;</SPAN><CODE>WriteRecords()</CODE><SPAN>&nbsp;method. That's it!</SPAN></P> <P>&nbsp;</P> <H4 id="the-main-application">The main application</H4> <P>Let's see now how we can load this plugin into the main application and leverage it. We're going to use reflection to dynamically load the DLL generated by our Class Library and discover the types and methods it implements. This is the required method to load the assembly:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-79" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> IPlugin <SPAN class="hljs-title">LoadAssembly</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">string</SPAN> assemblyPath</SPAN>)</SPAN> { <SPAN class="hljs-keyword">string</SPAN> assembly = Path.GetFullPath(assemblyPath); Assembly ptrAssembly = Assembly.LoadFile(assembly); <SPAN class="hljs-keyword">foreach</SPAN> (Type item <SPAN class="hljs-keyword">in</SPAN> ptrAssembly.GetTypes()) { <SPAN class="hljs-keyword">if</SPAN> (!item.IsClass) <SPAN class="hljs-keyword">continue</SPAN>; <SPAN class="hljs-keyword">if</SPAN> (item.GetInterfaces().Contains(<SPAN class="hljs-keyword">typeof</SPAN>(IPlugin))) { <SPAN class="hljs-keyword">return</SPAN> (IPlugin)Activator.CreateInstance(item); } } <SPAN class="hljs-keyword">throw</SPAN> <SPAN class="hljs-keyword">new</SPAN> Exception(<SPAN class="hljs-string">"Invalid DLL, Interface not found!"</SPAN>); } </CODE></PRE> <P>The parameter passed to the method is the path of the DLL. Once it has been loaded with the<SPAN>&nbsp;</SPAN><CODE>LoadFile()</CODE><SPAN>&nbsp;</SPAN>method exposed by the<SPAN>&nbsp;</SPAN><CODE>Assembly</CODE><SPAN>&nbsp;</SPAN>class, we iterate through the types exposed by the library. Since we have defined a common interface,<SPAN>&nbsp;</SPAN><CODE>IPlugin</CODE>, we look specifically for types which implement it. Once we find it, we create an instance of the class using the<SPAN>&nbsp;</SPAN><CODE>Activator.CreateInstance()</CODE><SPAN>&nbsp;</SPAN>method. Now we have a reference to the plugin, so we can use it through our application to invoke the<SPAN>&nbsp;</SPAN><CODE>Execute()</CODE><SPAN>&nbsp;</SPAN>method so that we can perform the CSV export.</P> <P>The application includes a menu to invoke the export feature. This is how the event handler connected to the menu item looks like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-101" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">exportAsCSVToolStripMenuItem_Click</SPAN>(<SPAN class="hljs-params"><SPAN class="hljs-keyword">object</SPAN> sender, EventArgs e</SPAN>)</SPAN> { SaveFileDialog saveFileDialog = <SPAN class="hljs-keyword">new</SPAN> SaveFileDialog(); saveFileDialog.Filter = <SPAN class="hljs-string">"CSV|*.csv"</SPAN>; saveFileDialog.Title = <SPAN class="hljs-string">"Save a CSV file"</SPAN>; saveFileDialog.ShowDialog(); <SPAN class="hljs-keyword">bool</SPAN> isFileSaved = plugin.Execute(employeeBindingSource.List, saveFileDialog.FileName); <SPAN class="hljs-keyword">if</SPAN> (isFileSaved) { MessageBox.Show(<SPAN class="hljs-string">"The CSV file has been exported with success"</SPAN>); } <SPAN class="hljs-keyword">else</SPAN> { MessageBox.Show(<SPAN class="hljs-string">"The export operation has failed"</SPAN>); } } </CODE></PRE> <P>The application uses a<SPAN>&nbsp;</SPAN><CODE>SaveFileDialog</CODE><SPAN>&nbsp;</SPAN>object to let the user choose where he wants to save the generated CSV file. Then we invoke the<SPAN>&nbsp;</SPAN><CODE>Execute()</CODE><SPAN>&nbsp;</SPAN>method exposed by the plugin, using the reference we have previously retrieved with the<SPAN>&nbsp;</SPAN><CODE>LoadAssembly()</CODE><SPAN>&nbsp;</SPAN>method. We pass two parameters: the list of employees (stored in the<SPAN>&nbsp;</SPAN><CODE>List</CODE><SPAN>&nbsp;</SPAN>property of the<SPAN>&nbsp;</SPAN><CODE>BindingSource</CODE><SPAN>&nbsp;</SPAN>object) and path of the file just selected by the user, which is stored in the<SPAN>&nbsp;</SPAN><CODE>FileName</CODE><SPAN>&nbsp;</SPAN>property of the<SPAN>&nbsp;</SPAN><CODE>SaveFileDialog</CODE><SPAN>&nbsp;</SPAN>object.</P> <P>That's it! We miss only one step: invoke the<SPAN>&nbsp;</SPAN><CODE>LoadAssembly()</CODE><SPAN>&nbsp;</SPAN>method passing the path of the DLL which includes our plugin. We do this when the application is loaded:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-125" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">LoadPlugins</SPAN>()</SPAN> { <SPAN class="hljs-keyword">try</SPAN> { <SPAN class="hljs-keyword">string</SPAN> dllPath = <SPAN class="hljs-string">$"<SPAN class="hljs-subst">{Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)}</SPAN>\\Contoso\\MyEmployees\\Plugins\\ExportDataLibrary.dll"</SPAN>; plugin = LoadAssembly(dllPath); exportAsCsvButton.Visible = <SPAN class="hljs-literal">true</SPAN>; } <SPAN class="hljs-keyword">catch</SPAN> (Exception) { exportMenu.Visible = <SPAN class="hljs-literal">false</SPAN>; } } </CODE></PRE> <P>The application expects to find the plugin in the<SPAN>&nbsp;</SPAN><CODE>C:\Program Files (x86)\Contoso\MyEmployees\Plugins</CODE><SPAN>&nbsp;</SPAN>folder. If it can find the DLL and load it properly, then the menu to export the CSV is made visible; otherwise, it stays hidden.</P> <P>&nbsp;</P> <P>The solution contains also a<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Windows Application Packaging Project</A>, which is used to package the Windows Forms application with MSIX.</P> <P>&nbsp;</P> <P>Now that we have an idea of the general architecture of the application, let's see how can we deploy the application and the plugin using the MSIX technology.</P> <P>&nbsp;</P> <H3 id="the-first-option-modification-packages">The first option: modification packages</H3> <P>Let's start with the simplest scenario, which doesn't require to change the code of the application. As such, we need to find a way to deploy the plugin in a separate way than the main application but, at the same time, make sure it can find it in the<SPAN>&nbsp;</SPAN><CODE>C:\Program Files (x86)\Contoso\MyEmployees\Plugins</CODE><SPAN>&nbsp;</SPAN>folder. Let's introduce<SPAN>&nbsp;</SPAN><STRONG>modification packages</STRONG>, which have already been discussed multiple times on this blog. A modification package is a special MSIX optional package, which doesn't contain a full application, but files and assets that can be leveraged by another application.</P> <P>&nbsp;</P> <P>The key feature of modification packages is that they share the same identity of the main application. As such, when you leverage MSIX features like the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Virtual File System</A><SPAN>&nbsp;</SPAN>or the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Virtual Registry</A>, Windows will treat them like if they belong to the same application. Let's see how this behavior can be leveraged for our scenario. We know that MSIX supports a feature called Virtual File System, which is implemented with a special folder called VFS that can be included inside a package. This folder can contain special folders (the full list is available<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">here</A>, which maps common system folders, like<SPAN>&nbsp;</SPAN><CODE>C:\Program Files</CODE>,<SPAN>&nbsp;</SPAN><CODE>C:\Windows</CODE>,<SPAN>&nbsp;</SPAN><CODE>C:\Windows\System32</CODE>, etc. When the application runs and looks for files in a system folder (like a dependency), Windows will look first in the VFS folder. This approach allows you to create self-contained MSIX packages, which don't require the user to manually install additional dependencies required by the application; additionally, it helps to solve the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">DLL Hell problem</A>, when multiple versions of the same dependency must coexist on the same machine.</P> <P>&nbsp;</P> <P>When we install a modification package, Windows will treat the VFS folder included inside it like if belongs to the main application. This means that, if we include our plugin inside the folder which maps<SPAN>&nbsp;</SPAN><CODE>C:\Program Files (x86)</CODE><SPAN>&nbsp;</SPAN>(which is called<SPAN>&nbsp;</SPAN><STRONG>ProgramFilesX86</STRONG>), the application will be able to find it without needing any code change.</P> <P>Let's see the real modification package, so that we can fully understand what I've just explained. You can download it from the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">GitHub repository</A>. The easiest way to edit is to install the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">MSIX Packaging Tool</A><SPAN>&nbsp;</SPAN>from<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">the Microsoft Store</A>. This way, you just need to right click on the package and choose<SPAN>&nbsp;</SPAN><STRONG>Edit with the MSIX Packaging Tool</STRONG><SPAN>&nbsp;</SPAN>to explore it.</P> <P>Let's start by taking a look at the manifest file, by clicking on the<SPAN>&nbsp;</SPAN><STRONG>Open file</STRONG><SPAN>&nbsp;</SPAN>button under the<SPAN>&nbsp;</SPAN><STRONG>Manifest file</STRONG><SPAN>&nbsp;</SPAN>section:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-160" class="language-xml hljs"><SPAN class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Package</SPAN> <SPAN class="hljs-attr">xmlns</SPAN>=<SPAN class="hljs-string">"http://schemas.microsoft.com/appx/manifest/foundation/windows10"</SPAN> <SPAN class="hljs-attr">xmlns:uap</SPAN>=<SPAN class="hljs-string">"http://schemas.microsoft.com/appx/manifest/uap/windows10"</SPAN> <SPAN class="hljs-attr">xmlns:uap4</SPAN>=<SPAN class="hljs-string">"http://schemas.microsoft.com/appx/manifest/uap/windows10/4"</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Identity</SPAN> <SPAN class="hljs-attr">Name</SPAN>=<SPAN class="hljs-string">"MyEmployees-ExportPlugin"</SPAN> <SPAN class="hljs-attr">Publisher</SPAN>=<SPAN class="hljs-string">"CN=Matteo Pagani"</SPAN> <SPAN class="hljs-attr">Version</SPAN>=<SPAN class="hljs-string">"1.0.3.0"</SPAN> <SPAN class="hljs-attr">ProcessorArchitecture</SPAN>=<SPAN class="hljs-string">"x64"</SPAN> /&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Properties</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">DisplayName</SPAN>&gt;</SPAN>MyEmployees (Export Plugin)<SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">DisplayName</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">PublisherDisplayName</SPAN>&gt;</SPAN>Contoso<SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">PublisherDisplayName</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Description</SPAN>&gt;</SPAN>Reserved<SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Description</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Logo</SPAN>&gt;</SPAN>Assets\StoreLogo.png<SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Logo</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Properties</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Resources</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Resource</SPAN> <SPAN class="hljs-attr">Language</SPAN>=<SPAN class="hljs-string">"en-us"</SPAN> /&gt;</SPAN> <SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Resources</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">Dependencies</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">TargetDeviceFamily</SPAN> <SPAN class="hljs-attr">Name</SPAN>=<SPAN class="hljs-string">"Windows.Desktop"</SPAN> <SPAN class="hljs-attr">MinVersion</SPAN>=<SPAN class="hljs-string">"10.0.17701.0"</SPAN> <SPAN class="hljs-attr">MaxVersionTested</SPAN>=<SPAN class="hljs-string">"10.0.17763.0"</SPAN> /&gt;</SPAN> <SPAN class="hljs-tag">&lt;<SPAN class="hljs-name">uap4:MainPackageDependency</SPAN> <SPAN class="hljs-attr">Name</SPAN>=<SPAN class="hljs-string">"MyEmployees"</SPAN> <SPAN class="hljs-attr">Publisher</SPAN>=<SPAN class="hljs-string">"CN=Matteo Pagani"</SPAN> /&gt;</SPAN> <SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Dependencies</SPAN>&gt;</SPAN> <SPAN class="hljs-tag">&lt;/<SPAN class="hljs-name">Package</SPAN>&gt;</SPAN> </CODE></PRE> <P>As you can see, exactly like a traditional MSIX package, it has an identity, a publisher, a version number, etc. However, there are two major key differences:</P> <UL id="pragma-line-182"> <LI id="pragma-line-182">It doesn't have an<SPAN>&nbsp;</SPAN><CODE>Application</CODE><SPAN>&nbsp;</SPAN>entry. Since it doesn't contain a full application, this package doesn't have an entry point.</LI> <LI id="pragma-line-183">Inside the<SPAN>&nbsp;</SPAN><CODE>Dependencies</CODE><SPAN>&nbsp;</SPAN>section we can find a<SPAN>&nbsp;</SPAN><CODE>MainPackageDependency</CODE><SPAN>&nbsp;</SPAN>entry, which specifies the name and the publisher of the main application it targets. If the application which identity is specified with the<SPAN>&nbsp;</SPAN><CODE>MainPackageDependency</CODE><SPAN>&nbsp;</SPAN>entry is not installed, Windows will refuse to install the optional package.</LI> </UL> <P>And what about the content? Move to the<SPAN>&nbsp;</SPAN><STRONG>Package files</STRONG><SPAN>&nbsp;</SPAN>section to explore the content of the package. We can see that the output of the<SPAN>&nbsp;</SPAN><STRONG>ExportDataLibrary</STRONG><SPAN>&nbsp;</SPAN>class library build has been copied inside the<SPAN>&nbsp;</SPAN><STRONG>VFS/ProgramFilesX86/Contoso/MyEmployees/Plugins</STRONG><SPAN>&nbsp;</SPAN>folder. This is the folder where the original code of the application expects to find the plugin.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VFS.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/166773iA5C49A26F1FCFBCA/image-size/large?v=v2&amp;px=999" role="button" title="VFS.png" alt="VFS.png" /></span></P> <P><SPAN>Let's see this in action.&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">This is the version of the app to use</A><SPAN>. Open the solution in Visual Studio, then right click on the&nbsp;</SPAN><STRONG>MyEmployees.Package</STRONG><SPAN>&nbsp;project and choose&nbsp;</SPAN><STRONG>Deploy</STRONG><SPAN>. Launch it and notice how the export menu is hidden, since the plugin DLL can't be found.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ExportMissing.png" style="width: 802px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/166774i193693377B25154A/image-size/large?v=v2&amp;px=999" role="button" title="ExportMissing.png" alt="ExportMissing.png" /></span></SPAN></P> <P>&nbsp;</P> <P><SPAN>Now double click on the modification package which you have just downloaded from&nbsp;<A href="#" target="_blank" rel="noopener">the repository</A>. The installation will proceed like with a regular MSIX package, excepts that the Windows won't offer you the option to launch the application since, well... we didn't install an application =)</img> For the same reason, if we open the Start menu we won't find the optional package listed in the list of programs. We will continue to find only the My Employees application. Where can we see the optional package? Right click on the My Employees entry in the Start menu and choose&nbsp;<STRONG>More → App Settings</STRONG>. Scroll down the settings page until you see a section titled&nbsp;<STRONG>App add-ons &amp; downloadable content</STRONG>. Here is our plugin!</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="PluginSettings.png" style="width: 506px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/166775i25BD02B0E7324E21/image-size/large?v=v2&amp;px=999" role="button" title="PluginSettings.png" alt="PluginSettings.png" /></span></SPAN></P> <P><SPAN>Now open the My Employees application. You can immediately see that the plugin has been recognized, since now the Export option is available:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ExportAvailable.png" style="width: 802px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/166776iBB79405CE57754D5/image-size/large?v=v2&amp;px=999" role="button" title="ExportAvailable.png" alt="ExportAvailable.png" /></span></SPAN></P> <P><SPAN>And if you click on the option, you will start the process to create a CSV and export it on a file on your local PC. Everything is working, without needing us to touch the code of the original application.</SPAN></P> <P>&nbsp;</P> <H3 id="option-2-using-an-optional-package">Option 2: using an Optional Package</H3> <P>The previous scenario is great for legacy applications or IT Pros who don't have access to the code, but it doesn't offer too much flexibility. We are required to leverage the Virtual File System to "fake" to the main application that our plugin is indeed installed in the<SPAN>&nbsp;</SPAN><CODE>C:\Program Files (x86)</CODE><SPAN>&nbsp;</SPAN>folder, which limits our opportunities to do more.</P> <P>&nbsp;</P> <P>If we are developers there's a better way to use the optional package which contains our plugin. The Universal Windows Platform, in fact, offers a set of APIs to query all the optional packages that are installed for an application. For each of them, we can get the identity, we can access to all the included files, etc.</P> <P>&nbsp;</P> <P>Let's start with analyzing the optional package. From an architectural point of view, there's no difference with the modification package we have seen before. A modification package, in fact, is just an optional package which, by leveraging the Virtual File System and the Virtual Registry, can make its content available to the main application without changing the code. A traditional optional package, in fact, doesn't need to leverage these features: the application is aware of the existence of optional packages and, as such, can freely load the files which are included. Other than that, they behave in the exact same way. They are both installed in the same way; they both appear in the<SPAN>&nbsp;</SPAN><STRONG>App add-ons &amp; downloadable content</STRONG><SPAN>&nbsp;</SPAN>section of the App Settings; they can be deployed independently from the main application.</P> <P>Also in this case you can find the optional package in the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">the repository</A>. If you edit it with the MSIX Packaging Tool, you will notice that it has the same exact manifest of the modification package. The only difference is the package content:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FilesOptionalPackage.png" style="width: 786px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/166777iB8DFA20CEC727237/image-size/large?v=v2&amp;px=999" role="button" title="FilesOptionalPackage.png" alt="FilesOptionalPackage.png" /></span></P> <P>&nbsp;</P> <P>Since we don't need to leverage anymore the VFS, we can just include the files which belong to our plugin in the root of the package. Let's see now how we can use this optional package in the Windows Forms application. As first step, we need to use some Windows 10 APIs, so we need to install the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Microsoft.Windows.Sdk.Contracts</A><SPAN>&nbsp;</SPAN>package from NuGet, which allows our .NET application to access to the API surface of the Universal Windows Platform. Once you have installed it, we can leverage the<SPAN>&nbsp;</SPAN><CODE>Package</CODE><SPAN>&nbsp;</SPAN>class included in the<SPAN>&nbsp;</SPAN><CODE>Windows.ApplicationModel</CODE><SPAN>&nbsp;</SPAN>namespace to query our optional packages.</P> <P>&nbsp;</P> <P>This is the code we're going to use to access to our plugin:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-221" class="language-csharp hljs"><SPAN class="hljs-function"><SPAN class="hljs-keyword">private</SPAN> <SPAN class="hljs-keyword">async</SPAN> Task <SPAN class="hljs-title">LoadOptionalPackagesAsync</SPAN>()</SPAN> { <SPAN class="hljs-keyword">if</SPAN> (Package.Current.Dependencies.Count &gt; <SPAN class="hljs-number">0</SPAN>) { <SPAN class="hljs-keyword">foreach</SPAN> (<SPAN class="hljs-keyword">var</SPAN> package <SPAN class="hljs-keyword">in</SPAN> Package.Current.Dependencies) { <SPAN class="hljs-keyword">if</SPAN> (package.Id.Name.Contains(<SPAN class="hljs-string">"ExportPlugin"</SPAN>)) { <SPAN class="hljs-keyword">var</SPAN> file = <SPAN class="hljs-keyword">await</SPAN> package.InstalledLocation.GetFileAsync(<SPAN class="hljs-string">"ExportDataLibrary.dll"</SPAN>); plugin = LoadAssembly(file.Path); exportAsCsvButton.Visible = <SPAN class="hljs-literal">true</SPAN>; } } } <SPAN class="hljs-keyword">else</SPAN> { exportAsCsvButton.Visible = <SPAN class="hljs-literal">false</SPAN>; } } </CODE></PRE> <P>All the optional packages are available through a collection called<SPAN>&nbsp;</SPAN><CODE>Dependencies</CODE>, exposed by the singleton<SPAN>&nbsp;</SPAN><CODE>Package.Current</CODE>. In our case we iterate through all the packages and, if we find one which name (stored in the<SPAN>&nbsp;</SPAN><CODE>Id.Name</CODE><SPAN>&nbsp;</SPAN>property) contains the<SPAN>&nbsp;</SPAN><CODE>ExportPlugin</CODE><SPAN>&nbsp;</SPAN>string, it means we can enable the export feature. All the files included in the optional package are available through the<SPAN>&nbsp;</SPAN><CODE>InstalledLocation</CODE><SPAN>&nbsp;</SPAN>property, which type is<SPAN>&nbsp;</SPAN><CODE>StorageFolder</CODE>. This means that we can use all the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Windows 10 Storage APIs</A><SPAN>&nbsp;</SPAN>to query, open and work with the files included in the optional package. In our case, we use the<SPAN>&nbsp;</SPAN><CODE>GetFileAsync()</CODE><SPAN>&nbsp;</SPAN>method to get a reference to the<SPAN>&nbsp;</SPAN><STRONG>ExportDataLibrary.dll</STRONG><SPAN>&nbsp;</SPAN>file. Then, we use the<SPAN>&nbsp;</SPAN><CODE>Path</CODE><SPAN>&nbsp;</SPAN>property as parameter to invoke the<SPAN>&nbsp;</SPAN><CODE>LoadAssembly()</CODE><SPAN>&nbsp;</SPAN>method and we save a reference to the plugin, so that we can use it later when the we need to use the CSV export function.</P> <P>&nbsp;</P> <P>As you can see, these APIs offer more flexibility compared to the modification package scenario. The MyEmployees application is very simple and we have developed only one plugin, but think to an application which can have hundreds of plugins. Thanks to these APIs we could provide a UI that displays all the available optional packages, allowing to the user to enable, disable or manage them.</P> <P>&nbsp;</P> <P>If you want to try also this scenario on your machine,<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">this is the folder</A><SPAN>&nbsp;</SPAN>which contains the MyEmployees version which uses the Optional Packages APIs.</P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>In this post we have seen two different options to support extensible applications with MSIX:</P> <UL id="pragma-line-254"> <LI id="pragma-line-254">Using modification packages, which leverage the Virtual File System and the Virtual Registry provided by the platform. This allows to leverage MSIX for plugins without asking the developer to change the code of the application. However, this approach is limited by the way the developer has created the plugin support. We are forced to follow his constraints and it's not granted that the way he has implemented plugin support will be fully compliant with modification packages.</LI> <LI id="pragma-line-255">Using optional packages, which require the developer to change the code of the application but they give you lot of flexibility. You can query for the installed optional packages; you can access to the included file; etc.</LI> </UL> <P>Before closing the post, there's an important caveat to keep in mind. You can't publish a modification package or an optional package on the Microsoft Store. As such, everything we have seen in this post applies only for enterprise distribution or sideloading.</P> <P>&nbsp;</P> <P>As usual, you can find the sample used in this post<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on GitHub</A>.</P> <P>&nbsp;</P> <P>Happy coding!</P> Fri, 24 Jan 2020 08:56:45 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/supporting-a-modular-windows-application-with-msix-and-optional/ba-p/1124865 Matteo Pagani 2020-01-24T08:56:45Z How to use JavaScript libraries on Uno Platform(WebAssembly) https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-to-use-javascript-libraries-on-uno-platform-webassembly/ba-p/1106524 <P>Uno Platform is one of the development platform tools for cross-platform apps such as UWP, Android, iOS, and WebAssembly.</P> <P>For WebAssembly, there are JavaScript interop features. I'll explain how to call JS functions from C# on this article.</P> <P>If you would like to call C# methods from JavaScript, then please check the following document:</P> <P><A href="#" target="_blank" rel="noopener">https://platform.uno/docs/articles/wasm-custom-events.html</A></P> <P>&nbsp;</P> <H2>Let's call a simple JS function</H2> <P>Preparation to call JavaScript functions is that copy .js file as Embedded resource under WasmScripts folder. If the library also has .css files, then copy those to WasmCSS folder too.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_0.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/165219i6083327BFD218D18/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_0.png" alt="clipboard_image_0.png" /></span></P> <P>In the case of the library provides simple JavaScript functions like below, you can call the functions using WebAssemblyRuntime.InvokeJS method.</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; #if __WASM__ using Uno.Foundation; #endif namespace JSInterop { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void InvokeJSFunctionButton_Click(object sender, RoutedEventArgs e) { #if __WASM__ WebAssemblyRuntime.InvokeJS("sayHello();"); #endif } } } </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>However, almost all real libraries provide them functions as a module. For example, if you would like to show toast notifications on the web browser, suppose you use iziToast.<BR />iziToast:&nbsp;<A href="#" target="_self">https://izitoast.marcelodolza.com/</A></P> <P>&nbsp;</P> <P>You will download it, and copy .js files and .css files as Embedded resource to the WASM project, like below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_4.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/165225iE766F83D2FBDAD0C/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_4.png" alt="clipboard_image_4.png" /></span></P> <P>And you will update your JavaScript file to use it:</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">function sayHello() { iziToast.show({ title: 'Info', message: 'Hello from the awesome library.' }); } </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>However, it doesn't work with the following error:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_3.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/165223iA8CC26128D0A930D/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_3.png" alt="clipboard_image_3.png" /></span></P> <P>Because a WASM app of Uno Platform includes require.js, so <A href="#" target="_self">iziToast detects it when loading</A>, and all functions is provided as a module that name is 'iziToast,' so you have to require it before using it.</P> <P>&nbsp;</P> <P>The correct code to use iziToast from Uno Platform is as below:</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">function sayHello() { require(['iziToast'], (iziToast) =&gt; { iziToast.show({ title: 'Info', message: 'Hello from the awesome library.' }); }); } </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>It works fine.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_5.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/165226iAF9E7DF93C625C6E/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_5.png" alt="clipboard_image_5.png" /></span></P> <H2>Conclusion</H2> <P>If you would like to call JavaScript functions from Uno Platform, then you should check that the library is a module or just functions or others.</P> <P>&nbsp;</P> <P>And if you feel Uno Platform is interesting, then please check the official site:</P> <P><A href="#" target="_self">https://platform.uno/</A></P> <P>&nbsp;</P> Tue, 14 Jan 2020 03:07:51 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/how-to-use-javascript-libraries-on-uno-platform-webassembly/ba-p/1106524 KazukiOta 2020-01-14T03:07:51Z React Native for Windows を試してみよう。そして型も手に入れよう。 https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/react-native-for-windows-%E3%82%92%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86-%E3%81%9D%E3%81%97%E3%81%A6%E5%9E%8B%E3%82%82%E6%89%8B%E3%81%AB%E5%85%A5%E3%82%8C%E3%82%88%E3%81%86/ba-p/1100495 <P>React Native for Windows を試してみたので備忘録もかねて手順をメモしてみました。</P> <P>作業開始時点の私の環境は以下のようになります。</P> <UL> <LI>Windows 10 Pro 1909 (設定アプリから開発者モードに変更してください)</LI> <LI>Visual Studio 2019 16.4.2 <UL> <LI>ユニバーサル Windows プラットフォームワークロードの <STRONG>C++(v141) ユニバーサル Windows プラットフォーム ツール</STRONG>。デフォルトでチェックが入っていないので入れる。142 じゃなくて 141 なので注意</LI> <LI>個別のコンポーネントから <STRONG>MSVC v141 - VS 2017 C++ ARM ビルドツール(v14.16)</STRONG>、<STRONG>MSVC v141 - VS 2017 C++ x64/x86 ビルド ツール (v14/16)</STRONG> の 2 つを入れる</LI> </UL> </LI> <LI>Node.js v12.9.1<STRONG> (注意:現時点で最新の v12.14.1 を使うとビルド時にエラーになりました)</STRONG></LI> <LI>yarn 1.21.1</LI> </UL> <P>まず、セットアップを以下のページに従ってやってみましょう。</P> <P><A href="#" target="_blank" rel="noopener">https://github.com/microsoft/react-native-windows/blob/master/vnext/docs/ConsumingRNW.md</A></P> <H2>プロジェクトを作る</H2> <P>npm install -g react-native-cli を実行して入れます。今日時点では react-native-cli@2.0.1 が入りました。</P> <P>このコマンドを使って React Native のプロジェクトを作ります。任意のフォルダーで以下のコマンドを実行します。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">react-native init ReactNativeHelloWorld --version 0.60.6</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><BR />初回実行は各種パッケージのダウンロードなどが走るので時間がかかりますがコーヒーでも飲んで待ちましょう。次に、ここに React Native for Windows を入れます。ここで入れる React Native for Windows は vnext と言われる C++ で実装されたパフォーマンスのいい方になります。yarn (もしくは npm)で入れることが出来ます。</P> <P>私は yarn なので、以下のコマンドで入れて React Native for Windows 関連のファイルを生成しました。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">yarn add rnpm-plugin-windows react-native windows --template vnext</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><BR />何か新バージョンの metro.config.js に置き換えるか?と聞かれたので y を選択して更新しました。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">Do you want to keep your metro.config.js or replace it with the latest version? If you ever made any changes to this file, you'll probably want to keep it. You can see the new version here: C:\Users\k_ota\Documents\Repos\runceel\ReactNativeHelloWorld\node_modules\react-native-windows\local-cli\generator-windows\templates\metro.config.js Do you want to replace metro.config.js? Answer y to replace, n to keep your version: y</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>windows フォルダーがプロジェクト直下に生成されて、その中にソリューションファイルがあります。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_0.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164664i17AED58DA69E1389/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_0.png" alt="clipboard_image_0.png" /></span></P> <P>これを開いて実行することも出来ますが、今回は Visual Studio Code で試してみたいと思います。</P> <P>Visual Studio Code を管理者として実行します。(ドキュメント上には記載はないのですが、管理者モードで実行しないと私の作業環境では、アプリのインストールに失敗しています。)Visual Studio Code で ReactNativeHelloWorld フォルダーを開きます。Visual Studio Code に React Native Tools をインストールしていない場合はインストールします。</P> <P>&nbsp;</P> <P>Visual Studio Code の launch.json を以下のようにして F5 を押してみましょう。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: <A href="#" target="_blank">https://go.microsoft.com/fwlink/?linkid=830387</A> "version": "0.2.0", "configurations": [ { "name": "Debug Windows", "cwd": "${workspaceFolder}", "type": "reactnative", "request": "launch", "platform": "windows" } ] }</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>初回起動は凄く時間がかかるのでコーヒーでも飲んで時間をつぶしましょう。ビルドの進捗は Visual Studio Code の Output から React native: Run windows で確認できます。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_0.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164687iBC1449F5AF4DCE22/image-size/large?v=v2&amp;px=999" role="button" title="clipboard_image_0.png" alt="clipboard_image_0.png" /></span></P> <P>暫くすると、以下のように Window が起動してきます。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164691i2FE1E741B39A457D/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_1.png" alt="clipboard_image_1.png" /></span></P> <P>&nbsp;</P> <H2>型が欲しい</H2> <P>TypeScript 対応してみましょう。既存のプロジェクトに TypeScript サポートを追加する手順が以下のドキュメントにあります。</P> <P><A href="#" target="_blank" rel="noopener">https://facebook.github.io/react-native/docs/typescript#adding-typescript-to-an-existing-project</A></P> <P>この手順に沿って、以下のコマンドを実行していきます。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">yarn add typescript @types/jest @types/react @types/react-native @types/react-test-renderer</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><BR />そして、tsconfig.json を以下の内容で作成します。一部ドキュメントと変えているのがテンプレートでは json ファイルを import しているので、それに対応するため resolveJsonModule を true にしています。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">{ "compilerOptions": { "allowJs": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "isolatedModules": true, "jsx": "react", "lib": ["es6"], "moduleResolution": "node", "noEmit": true, "strict": true, "target": "esnext", "resolveJsonModule": true }, "exclude": [ "node_modules", "babel.config.js", "metro.config.js", "jest.config.js" ] }</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>jest.config.js も以下の内容で追加します。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">module.exports = { preset: 'react-native', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], }; </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>最後に、App.js と index.js を App.tsx と index.tsx にします。そうすると App.tsx で Cannot find name 'global' というエラーになります。とりあえずないものは作ればいいので、以下の定義を App.tsx の import 文の下あたりに追加します。(参考にした Issue:&nbsp;<A href="#" target="_blank" rel="noopener">https://github.com/facebook/react-native/issues/26598</A>)</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">declare var global: {HermesInternal: null | {}}; </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P><BR />ここまで出来たら再び F5 を押して実行してみます。以下のように表示されたら成功です!</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_0.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164695i61BE6F7121EF25A3/image-size/large?v=v2&amp;px=999" role="button" title="clipboard_image_0.png" alt="clipboard_image_0.png" /></span></P> <P>&nbsp;</P> <H2>補完を体験してみよう</H2> <P>App.tsx を以下のように変更してみましょう。</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="javascript">/** * Sample React Native App * <A href="#" target="_blank">https://github.com/facebook/react-native</A> * * @format * @flow */ import React, {Fragment, useState} from 'react'; import { SafeAreaView, StyleSheet, ScrollView, View, Text, StatusBar, Button, } from 'react-native'; import { Header, LearnMoreLinks, Colors, DebugInstructions, ReloadInstructions, } from 'react-native/Libraries/NewAppScreen'; declare var global: {HermesInternal: null | {}}; const App = () =&gt; { const [count, setCount] = useState(0); return ( &lt;Fragment&gt; &lt;StatusBar barStyle="dark-content" /&gt; &lt;SafeAreaView&gt; &lt;ScrollView contentInsetAdjustmentBehavior="automatic" style={styles.scrollView}&gt; &lt;Header /&gt; &lt;View style={styles.body}&gt; &lt;Text style={styles.sectionTitle}&gt; The counter value is {count}. &lt;/Text&gt; &lt;Button onPress={() =&gt; setCount(count + 1)} title="Increment" /&gt; &lt;/View&gt; &lt;/ScrollView&gt; &lt;/SafeAreaView&gt; &lt;/Fragment&gt; ); }; const styles = StyleSheet.create({ scrollView: { backgroundColor: Colors.lighter, }, engine: { position: 'absolute', right: 0, }, body: { backgroundColor: Colors.white, }, sectionContainer: { marginTop: 32, paddingHorizontal: 24, }, sectionTitle: { fontSize: 24, fontWeight: '600', color: Colors.black, }, sectionDescription: { marginTop: 8, fontSize: 18, fontWeight: '400', color: Colors.dark, }, highlight: { fontWeight: '700', }, footer: { color: Colors.dark, fontSize: 12, fontWeight: '600', padding: 4, paddingRight: 12, textAlign: 'right', }, }); export default App; </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>コードを書いている途中にちゃんと補完がきくことが体験できますね!(JS でも出るんでしょうけど)</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164697iC162A5AE62960288/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_1.png" alt="clipboard_image_1.png" /></span></P> <P>うまくいっていると、アプリがリアルタイムに書き換わっていて以下のように動くはずです。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="rn4w.gif" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164698i9281710A20A4A8E7/image-size/medium?v=v2&amp;px=400" role="button" title="rn4w.gif" alt="rn4w.gif" /></span></P> <H2>まとめ</H2> <P>軽く React Native for Windows を試してみましたが、環境さえ整っていれば意外と簡単に動きました。同僚の Matteo が最近以下の React Native for Windows 系の記事を書いてるので興味があったら是非チェックしてみてください。</P> <UL> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/getting-started-with-react-native-for-windows/ba-p/912093" target="_self">Getting started with React Native for Windows</A></LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-a-react-native-module-for-windows/ba-p/1067893" target="_self">Building a React Native module for Windows</A></LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_self">Create a CI/CD pipeline for a React Native for Windows application</A></LI> <LI><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/react-native-for-windows-and-native-modules-how-to-add-ci-cd-to/ba-p/1085473" target="_self">React Native for Windows and native modules: how to add CI/CD to your project</A></LI> </UL> <P>それでは!</P> <P>ソースコードは以下のリポジトリーに上げてます。</P> <P><A href="#" target="_blank" rel="noopener">https://github.com/runceel/HelloRN4W</A></P> <P>&nbsp;</P> Tue, 14 Jan 2020 01:07:24 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/react-native-for-windows-%E3%82%92%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86-%E3%81%9D%E3%81%97%E3%81%A6%E5%9E%8B%E3%82%82%E6%89%8B%E3%81%AB%E5%85%A5%E3%82%8C%E3%82%88%E3%81%86/ba-p/1100495 KazukiOta 2020-01-14T01:07:24Z Using Azure DevOps to create a CI/CD pipeline for an Android application built with React Native https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/using-azure-devops-to-create-a-ci-cd-pipeline-for-an-android/ba-p/1094422 <P><A href="https://gorovian.000webhostapp.com/?exam=t5/tag/react%20native/tg-p/board-id/WindowsDevAppConsult" target="_blank">In the previous posts</A><SPAN>&nbsp;</SPAN>I have written on this blog I have talked about many specific tasks related to React Native for Windows: how to build a native module, how to setup a CI/CD pipeline on Azure DevOps, etc. However, one of the advantages of React Native is the ability to target multiple platforms leveraging the same codebase and, at the same time, achieve the same quality of a native application. This is made possible by the different render targets, which translates the various JSX controls into native controls of the platform where the application is running. And thanks to native modules, we can also build plugins which, under the hood, use Android, iOS or Windows APIs to perform a task.</P> <P>&nbsp;</P> <P>As such, after having successfully built a CI/CD pipeline on Azure DevOps for automatically build and deploy my React Native project for Windows, I decided to move to the next step and build such a pipeline also for the other platforms. The goal is to have a Windows, Android and iOS application automatically deployed every time I commit some code to the repository which hosts my React Native project.</P> <P>&nbsp;</P> <P>Let's start to see how we can add Android support to our pipeline!</P> <P>&nbsp;</P> <H3 id="welcome-gradle">Welcome Gradle</H3> <P>If you have ever tried to deploy your React Native application on Android, you should already have met<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Gradle</A>. It's a very popular build automation system, widely used for Java projects. You can think of it like MSBuild, but for platforms different than C# and .NET. Exactly like MSBuild, Gradle allows to setup, trough properties and configuration files, complex build systems, which require additional tasks other than just compiling the source code. This is the reason why React Native uses Gradle for the Android platform: it's able to perform all the tasks which are required to bundle and package together the JavaScript files, other than just compiling the Java code into an APK package. When you run your React Native project for Android using the following command:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-13" class="language-powershell hljs">react-native run-android </CODE></PRE> <P>the React Native CLI will run Gradle to build the APK and then it will launch the Metro packager, so that you can have a live development and debugging experience. If you use the React Native CLI, instead, to generate a release package, Gradle will generate a bundle first and then it will create a standalone APK package. It isn't a very different experience from what we did<SPAN>&nbsp;</SPAN><A href="#" target="_blank">in the previous posts for the Windows project</A>. In that case, it was MSBuild to take care of everything for us and to generate a MSIX package with all the required files.</P> <P>&nbsp;</P> <P>The Android build process is controlled by a file called<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG>, which defines all the tasks that must be performed during the compilation. Gradle support a scripting language, so you can define variables, read properties, add conditions and logic, etc.</P> <P>&nbsp;</P> <P>In case of a React Native project, Gradle leverages two different<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>files:</P> <UL id="pragma-line-24"> <LI id="pragma-line-24">A<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file inside the<SPAN>&nbsp;</SPAN><STRONG>android</STRONG><SPAN>&nbsp;</SPAN>folder, which is the top-level build file where you can add configuration options common to all sub-projects/modules.</LI> <LI id="pragma-line-25">A<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file inside the<SPAN>&nbsp;</SPAN><STRONG>android/app</STRONG><SPAN>&nbsp;</SPAN>folder, which instead contains the various tasks and properties which are used to generate the APK.</LI> </UL> <P>Understanding the basics of Gradle is very important, since we'll need to make a few changes to the default<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>in order to make the project suitable for a CI/CD pipeline. Let' see them!</P> <P>&nbsp;</P> <H4 id="create-a-signing-certificate">Create a signing certificate</H4> <P>Exactly like we did in our Windows application, also Android applications must be signed with a valid certificate in order to be installed. As such, the first step is to create a private key that will be used for this task. We can do it using a tool which comes with the Java SDK called<SPAN>&nbsp;</SPAN><STRONG>keytool</STRONG>. If you have the JDK installed on your machine, you will find it in the<SPAN>&nbsp;</SPAN><STRONG>C:\Program Files\Java\jdkx.x.x_x\bin</STRONG><SPAN>&nbsp;</SPAN>folder, where<SPAN>&nbsp;</SPAN><STRONG>x.x.x._x</STRONG><SPAN>&nbsp;</SPAN>is the JDK version. If you have Visual Studio with the Xamarin tools installed, you can also open Visual Studio and choose<SPAN>&nbsp;</SPAN><STRONG>Tools → Android → Android Adb Command Prompt</STRONG>. This option will open a command prompt with the JDK path included in the global paths, so you will be able to invoke the keytool command from any location.</P> <P>&nbsp;</P> <P>Regardless of the option you choose, this is the command you'll need to type:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-34" class="language-powershell hljs">keytool -genkeypair -v -keystore my-upload-key.keystore -alias my-key-alias -keyalg RSA -keysize <SPAN class="hljs-number">2048</SPAN> -validity <SPAN class="hljs-number">10000</SPAN> </CODE></PRE> <P>Two parameters can be customized here:</P> <UL id="pragma-line-40"> <LI id="pragma-line-40"><CODE>keystore</CODE><SPAN>&nbsp;</SPAN>defines the name of the file that will be generated. The previous command will generate a file called<SPAN>&nbsp;</SPAN><STRONG>my-upload-key.keystore</STRONG></LI> <LI id="pragma-line-41"><CODE>alias</CODE><SPAN>&nbsp;</SPAN>defines the alias that will be assigned to the key. The previous command will generate an alias called<SPAN>&nbsp;</SPAN><STRONG>my-key-alias</STRONG>. Feel free to replace it with the one you prefer, as long as you note it somewhere because you will need it to perform the signing.</LI> </UL> <P>After pressing Enter, you will be asked for the following information:</P> <UL id="pragma-line-45"> <LI id="pragma-line-45">The password to use to protect the certificate store</LI> <LI id="pragma-line-46">First and last name</LI> <LI id="pragma-line-47">Organizational Unit</LI> <LI id="pragma-line-48">Organization</LI> <LI id="pragma-line-49">City or locality</LI> <LI id="pragma-line-50">State or province</LI> <LI id="pragma-line-51">Country code</LI> </UL> <P>These information will be used to generate the subject of the certificate. For example, in my case the generated subject was:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-55" class="language-text hljs">CN=Matteo Pagani, OU=AppConsult, O=Microsoft, L=Como, ST=CO, C=IT </CODE></PRE> <P>Once you have confirmed, you will be asked for the password to protect the key, which can be a different one or the same you have previously used to protect the certificate store. At the end of the process, the tool will create a file in the same folder called<SPAN>&nbsp;</SPAN><STRONG>my-upload-key.keystore</STRONG><SPAN>&nbsp;</SPAN>with the private key. Make sure to store it in a safe place. We're going to need it later to sign the APK as part of our pipeline.</P> <P>&nbsp;</P> <H4 id="setup-the-signing">Setup the signing</H4> <P>By default, React Native signs the generated APK as part of the build process. We can see this task in the second<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file, the one inside the<SPAN>&nbsp;</SPAN><STRONG>android/app</STRONG><SPAN>&nbsp;</SPAN>folder. Open it with a text editor of your choice and look for a section called<SPAN>&nbsp;</SPAN><CODE>android</CODE>. You will find the following entry:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-64" class="language-java hljs">signingConfigs { debug { <SPAN class="hljs-function">storeFile <SPAN class="hljs-title">file</SPAN><SPAN class="hljs-params">(<SPAN class="hljs-string">'debug.keystore'</SPAN>)</SPAN> storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } buildTypes </SPAN>{ debug { signingConfig signingConfigs.debug } release { <SPAN class="hljs-comment">// Caution! In production, you need to generate your own keystore file.</SPAN> <SPAN class="hljs-comment">// see https://facebook.github.io/react-native/docs/signed-apk-android.</SPAN> signingConfig signingConfigs.<SPAN class="hljs-function">debug minifyEnabled enableProguardInReleaseBuilds proguardFiles <SPAN class="hljs-title">getDefaultProguardFile</SPAN><SPAN class="hljs-params">(<SPAN class="hljs-string">"proguard-android.txt"</SPAN>)</SPAN>, "proguard-rules.pro" } } </SPAN></CODE></PRE> <P>The<SPAN>&nbsp;</SPAN><CODE>signingConfigs</CODE><SPAN>&nbsp;</SPAN>section includes a signing configuration called<SPAN>&nbsp;</SPAN><CODE>debug</CODE>, which defines the certificate to use, the credentials, etc. In the<SPAN>&nbsp;</SPAN><CODE>buildTypes</CODE><SPAN>&nbsp;</SPAN>section, instead, we can find different settings that are applied based on the configuration we're using for the build:<SPAN>&nbsp;</SPAN><CODE>debug</CODE><SPAN>&nbsp;</SPAN>or<SPAN>&nbsp;</SPAN><CODE>release</CODE>. You can notice that, by default, the same signing configuration we have just seen (the one called<SPAN>&nbsp;</SPAN><CODE>signingConfigs.debug</CODE>) is used both for debug and release. If it's fine to keep the debug one, when you move to a CI/CD pipeline, instead, you will need to remove the signing operation for the<SPAN>&nbsp;</SPAN><CODE>release</CODE><SPAN>&nbsp;</SPAN>configuration.</P> <P>&nbsp;</P> <P>The reason should be pretty clear: to leverage this approach we would need to upload to our repository the private key and to include, in clear, both the store password and the key password. If this approach is fine for the debug configuration since React Native uses a generic test certificate, it isn't a very safe approach when it comes to use our private certificate. As such, before starting to build our pipeline, let's remove the following line from the<SPAN>&nbsp;</SPAN><CODE>buildTypes.release</CODE><SPAN>&nbsp;</SPAN>section:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-92" class="language-java hljs">signingConfig signingConfigs.debug </CODE></PRE> <P>This is how the final result should look like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-98" class="language-java hljs">signingConfigs { debug { <SPAN class="hljs-function">storeFile <SPAN class="hljs-title">file</SPAN><SPAN class="hljs-params">(<SPAN class="hljs-string">'debug.keystore'</SPAN>)</SPAN> storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } buildTypes </SPAN>{ debug { signingConfig signingConfigs.debug } release { <SPAN class="hljs-function">minifyEnabled enableProguardInReleaseBuilds proguardFiles <SPAN class="hljs-title">getDefaultProguardFile</SPAN><SPAN class="hljs-params">(<SPAN class="hljs-string">"proguard-android.txt"</SPAN>)</SPAN>, "proguard-rules.pro" } } </SPAN></CODE></PRE> <P>With this configuration, we're going to generate a release APK which is unsigned. We're going to sign it with a dedicated Azure DevOps task, which will allow us to keep our private key and the passwords safe.</P> <P>&nbsp;</P> <H4 id="setting-up-the-build-number">Setting the build number</H4> <P>Exactly like we did for the Windows application, it's best to generate a new APK with a higher version number than the previous version. However, in our scenario, things are slightly more complicated than what we did for Windows. In that case, it was enough to use an Azure DevOps task to inject inside the manifest of the Windows application the correct version number. In our scenario, instead, it won't work, since the application version is set during the Gradle build. Even if we would add a task in the CI pipeline to set the version number in the manifest of the Android application, it would be overwritten by the one defined in the<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file during the build process.</P> <P>&nbsp;</P> <P>As such, the best option is to inject the version number directly in the<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file. If you're worried that this means that you'll need to manually manipulate the Gradle file with a PowerShell script or something similar, you can relax! We have mentioned before that Gradle is very powerful and that the build files can contain properties. These properties can be retrieved from many sources: from the system (like an environment variable), from another file or... from a command line parameter. That's what we need! Azure DevOps is going to invoke the Gradle executable passing, as parameter, the version number. Inside the<SPAN>&nbsp;</SPAN><STRONG>gradle.build</STRONG><SPAN>&nbsp;</SPAN>file we'll include the logic to read the value from the parameter and to set it as version number in the manifest.</P> <P>&nbsp;</P> <P>Let's start by opening the<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file contained in the<SPAN>&nbsp;</SPAN><STRONG>android</STRONG><SPAN>&nbsp;</SPAN>folder. You will find a section that defines some basic information which are stored in the manifest, like the minimum and maximum supported SDK. This section is called<SPAN>&nbsp;</SPAN><CODE>ext</CODE><SPAN>&nbsp;</SPAN>and it's included in a section called<SPAN>&nbsp;</SPAN><CODE>buildscript</CODE>:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-127" class="language-java hljs">ext { buildToolsVersion = <SPAN class="hljs-string">"28.0.3"</SPAN> minSdkVersion = <SPAN class="hljs-number">16</SPAN> compileSdkVersion = <SPAN class="hljs-number">28</SPAN> targetSdkVersion = <SPAN class="hljs-number">28</SPAN> supportLibVersion = <SPAN class="hljs-string">"28.0.0"</SPAN> } </CODE></PRE> <P>We're going to add two new properties, to store the version name and the version code, which are the two values used by Android to represent a version number. Version code is the real unique identifier and it's an integer, which must be incremented every time (for example, 56). Version name, instead, is a string and represents the version number you would like to display to your users (for example, 15.2.3). But first, inside the<SPAN>&nbsp;</SPAN><CODE>buildscript</CODE><SPAN>&nbsp;</SPAN>section, let's add two methods which are going to retrieve the value of the command line parameters:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-140" class="language-java hljs">def getMyVersionCode = { -&gt; def code = project.hasProperty(<SPAN class="hljs-string">'versionCode'</SPAN>) ? versionCode.toInteger() : -<SPAN class="hljs-number">1</SPAN> println <SPAN class="hljs-string">"VersionCode is set to $code"</SPAN> <SPAN class="hljs-keyword">return</SPAN> code } def getMyVersionName = { -&gt; def name = project.hasProperty(<SPAN class="hljs-string">'versionName'</SPAN>) ? versionName : <SPAN class="hljs-string">"1.0"</SPAN> println <SPAN class="hljs-string">"VersionName is set to $name"</SPAN> <SPAN class="hljs-keyword">return</SPAN> name } </CODE></PRE> <P>Both method behaves in the same way. With the<SPAN>&nbsp;</SPAN><CODE>property.hasProperty()</CODE><SPAN>&nbsp;</SPAN>function we can retrieve the value of a parameter passed to the Gradle task. The first one will look for a command line parameter called<SPAN>&nbsp;</SPAN><STRONG>versionCode</STRONG>, while the second one for a parameter called<SPAN>&nbsp;</SPAN><STRONG>versionName</STRONG>. If they aren't found, we're going to set a default value. Then we print the value (it will be helpful for logging purposes) and we return it to the caller.</P> <P>&nbsp;</P> <P>Now we can add two properties in the<SPAN>&nbsp;</SPAN><CODE>ext</CODE><SPAN>&nbsp;</SPAN>section, which value will be retrieved using these two functions:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-158" class="language-java hljs">ext { buildToolsVersion = <SPAN class="hljs-string">"28.0.3"</SPAN> minSdkVersion = <SPAN class="hljs-number">16</SPAN> compileSdkVersion = <SPAN class="hljs-number">28</SPAN> targetSdkVersion = <SPAN class="hljs-number">28</SPAN> supportLibVersion = <SPAN class="hljs-string">"28.0.0"</SPAN> versionName = getMyVersionName() versionCode = getMyVersionCode() } </CODE></PRE> <P>This is how your entire<SPAN>&nbsp;</SPAN><CODE>buildscript</CODE><SPAN>&nbsp;</SPAN>section should look like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-172" class="language-java hljs">buildscript { def getMyVersionCode = { -&gt; def code = project.hasProperty(<SPAN class="hljs-string">'versionCode'</SPAN>) ? versionCode.toInteger() : -<SPAN class="hljs-number">1</SPAN> println <SPAN class="hljs-string">"VersionCode is set to $code"</SPAN> <SPAN class="hljs-keyword">return</SPAN> code } def getMyVersionName = { -&gt; def name = project.hasProperty(<SPAN class="hljs-string">'versionName'</SPAN>) ? versionName : <SPAN class="hljs-string">"1.0"</SPAN> println <SPAN class="hljs-string">"VersionName is set to $name"</SPAN> <SPAN class="hljs-keyword">return</SPAN> name } ext { buildToolsVersion = <SPAN class="hljs-string">"28.0.3"</SPAN> minSdkVersion = <SPAN class="hljs-number">16</SPAN> compileSdkVersion = <SPAN class="hljs-number">28</SPAN> targetSdkVersion = <SPAN class="hljs-number">28</SPAN> supportLibVersion = <SPAN class="hljs-string">"28.0.0"</SPAN> versionName = getMyVersionName() versionCode = getMyVersionCode() } repositories { google() jcenter() } dependencies { classpath(<SPAN class="hljs-string">"com.android.tools.build:gradle:3.4.1"</SPAN>) <SPAN class="hljs-comment">// <SPAN class="hljs-doctag">NOTE:</SPAN> Do not place your application dependencies here; they belong</SPAN> <SPAN class="hljs-comment">// in the individual module build.gradle files</SPAN> } } </CODE></PRE> <P>Now we need to open the other<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file, the one inside the<SPAN>&nbsp;</SPAN><STRONG>android/app</STRONG><SPAN>&nbsp;</SPAN>folder of your project. You will find a section called<SPAN>&nbsp;</SPAN><CODE>defaultConfig</CODE><SPAN>&nbsp;</SPAN>inside the<SPAN>&nbsp;</SPAN><CODE>android</CODE><SPAN>&nbsp;</SPAN>block:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-211" class="language-java hljs">defaultConfig { applicationId <SPAN class="hljs-string">"com.navigationsample"</SPAN> minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode <SPAN class="hljs-number">1</SPAN> versionName <SPAN class="hljs-string">"1.0"</SPAN> } </CODE></PRE> <P>We will need to replace the fixed value assigned to the<SPAN>&nbsp;</SPAN><CODE>versionCode</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>versionName</CODE><SPAN>&nbsp;</SPAN>properties with the ones coming from the other<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file. We can see, in the other properties, an example of how to achieve this goal. Since the other file is referenced by the current one, we can access to its properties simply by using the<SPAN>&nbsp;</SPAN><CODE>rootProject.ext.propertyName</CODE><SPAN>&nbsp;</SPAN>syntax. This is the final result:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-224" class="language-java hljs">defaultConfig { applicationId <SPAN class="hljs-string">"com.navigationsample"</SPAN> minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName } </CODE></PRE> <P>That's it. Now we just need to remember to pass to the Gradle executable the<SPAN>&nbsp;</SPAN><CODE>versionCode</CODE><SPAN>&nbsp;</SPAN>and the<SPAN>&nbsp;</SPAN><CODE>versionName</CODE><SPAN>&nbsp;</SPAN>parameters, so that they can be injected in the default configuration. We will do it in a moment, when we will start to build our pipeline.</P> <P>&nbsp;</P> <H3 id="create-the-ci-pipeline-on-azure-devops">Create the CI pipeline on Azure DevOps</H3> <P>We're going to use again<SPAN>&nbsp;</SPAN><A href="#" target="_blank">my sample project</A><SPAN>&nbsp;</SPAN>as a starting point. First, go to the<SPAN>&nbsp;</SPAN><STRONG>Pipelines</STRONG><SPAN>&nbsp;</SPAN>section and create a new pipeline. You're going to connect it to the same repository you have used for the Windows application but, this time, choose<SPAN>&nbsp;</SPAN><STRONG>Android</STRONG><SPAN>&nbsp;</SPAN>as a starting template (you will need to click on<SPAN>&nbsp;</SPAN><STRONG>Show more</STRONG><SPAN>&nbsp;</SPAN>at the bottom to see it). The default YAML is pretty simple:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-239" class="language-yaml hljs"><SPAN class="hljs-comment"># Android</SPAN> <SPAN class="hljs-comment"># Build your Android project with Gradle.</SPAN> <SPAN class="hljs-comment"># Add steps that test, sign, and distribute the APK, save build artifacts, and more:</SPAN> <SPAN class="hljs-comment"># https://docs.microsoft.com/azure/devops/pipelines/languages/android</SPAN> <SPAN class="hljs-attr">trigger:</SPAN> <SPAN class="hljs-bullet">-</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-attr">pool:</SPAN> <SPAN class="hljs-attr"> vmImage:</SPAN> <SPAN class="hljs-string">'macos-latest'</SPAN> <SPAN class="hljs-attr">steps:</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">Gradle@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> workingDirectory:</SPAN> <SPAN class="hljs-string">''</SPAN> <SPAN class="hljs-attr"> gradleWrapperFile:</SPAN> <SPAN class="hljs-string">'gradlew'</SPAN> <SPAN class="hljs-attr"> gradleOptions:</SPAN> <SPAN class="hljs-string">'-Xmx3072m'</SPAN> <SPAN class="hljs-attr"> publishJUnitResults:</SPAN> <SPAN class="hljs-literal">false</SPAN> <SPAN class="hljs-attr"> testResultsFiles:</SPAN> <SPAN class="hljs-string">'**/TEST-*.xml'</SPAN> <SPAN class="hljs-attr"> tasks:</SPAN> <SPAN class="hljs-string">'assembleDebug'</SPAN> </CODE></PRE> <P>It contains just a Gradle task. As you can imagine, we will need to do a few tweaks to make it good enough for our application =)</img> Let's start!</P> <P>&nbsp;</P> <H4 id="choose-the-hosted-agent">Choose the hosted agent</H4> <P>Out of the box, the template uses a hosted agent based on MacOS. That's perfectly fine but, if you have any reason to do it, feel free to switch to a Windows or a Linux one. Android compilation is supported by all the operating systems, so you can use the one you prefer.</P> <P>&nbsp;</P> <H4 id="set-the-build-number">Set the build number</H4> <P>I want to leverage for the Android version the same build number I'm using for the Windows version, which we're going to inject inside the manifest using the Gradle parameters we have previously defined. As such, exactly like I did for the Windows pipeline, I'm going to change the default Azure DevOps build number by setting the<SPAN>&nbsp;</SPAN><CODE>name</CODE><SPAN>&nbsp;</SPAN>property:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-271" class="language-yaml hljs"><SPAN class="hljs-attr">name:</SPAN> <SPAN class="hljs-string">$(date:yyyy).$(Month)$(rev:.r)</SPAN> </CODE></PRE> <P>This configuration will generate a build number like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-277" class="language-yaml hljs"><SPAN class="hljs-number">2020.1</SPAN><SPAN class="hljs-number">.15</SPAN></CODE></PRE> <H4>&nbsp;</H4> <H4 id="install-the-dependencies">Install the dependencies</H4> <P>Also this step isn't very different from the one we did to compile the Windows application. Before compiling the Android version, we need to make sure all the dependencies used by our React Native project are installed. As such, we're going to leverage Yarn once again with the following script:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-284" class="language-yaml hljs"><SPAN class="hljs-attr">- script:</SPAN> <SPAN class="hljs-string">yarn</SPAN> <SPAN class="hljs-string">install</SPAN></CODE></PRE> <P>&nbsp;</P> <H4 id="setup-the-gradle-build">Setup the Gradle build</H4> <P>The Gradle task will take care of running the build, by using the configuration defined in the various<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>files. However, compared to the default task included in the template, we need to add a few options:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-291" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">Gradle@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> gradleWrapperFile:</SPAN> <SPAN class="hljs-string">'android/gradlew'</SPAN> <SPAN class="hljs-attr"> workingDirectory:</SPAN> <SPAN class="hljs-string">'android/'</SPAN> <SPAN class="hljs-attr"> options:</SPAN> <SPAN class="hljs-string">'-PversionName=$(Build.BuildNumber) -PversionCode=$(Build.BuildId)'</SPAN> <SPAN class="hljs-attr"> tasks:</SPAN> <SPAN class="hljs-string">'assembleRelease'</SPAN> <SPAN class="hljs-attr"> publishJUnitResults:</SPAN> <SPAN class="hljs-literal">false</SPAN> <SPAN class="hljs-attr"> javaHomeOption:</SPAN> <SPAN class="hljs-string">'JDKVersion'</SPAN> <SPAN class="hljs-attr"> gradleOptions:</SPAN> <SPAN class="hljs-string">'-Xmx3072m'</SPAN> </CODE></PRE> <P>Here are the changes I've made:</P> <P>&nbsp;</P> <OL id="pragma-line-305"> <LI id="pragma-line-305"> <P>We need to specify where<SPAN>&nbsp;</SPAN><STRONG>gradlew</STRONG><SPAN>&nbsp;</SPAN>(the wrapper which launches the Gradle executable) is located through the<SPAN>&nbsp;</SPAN><CODE>gradleWrapperFile</CODE><SPAN>&nbsp;</SPAN>property. Typically it's stored in the root of the project. However, in our scenario, we're working with a React Native project, so the<SPAN>&nbsp;</SPAN><STRONG>gradlew</STRONG><SPAN>&nbsp;</SPAN>wrapper is stored inside the<SPAN>&nbsp;</SPAN><STRONG>android</STRONG><SPAN>&nbsp;</SPAN>folder and not in the root. For the same reason, we need to set also the<SPAN>&nbsp;</SPAN><CODE>workingDirectory</CODE><SPAN>&nbsp;</SPAN>property to<SPAN>&nbsp;</SPAN><STRONG>android</STRONG>.</P> </LI> <LI id="pragma-line-306"> <P>We need to pass, as parameters, the<SPAN>&nbsp;</SPAN><STRONG>versionName</STRONG><SPAN>&nbsp;</SPAN>and the<SPAN>&nbsp;</SPAN><STRONG>versionCode</STRONG>, so that they can be injected inside the manifest as per the logic we have previously added in the<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file. We set them in the<SPAN>&nbsp;</SPAN><STRONG>options</STRONG><SPAN>&nbsp;</SPAN>section of the task configuration. Command line parameters must be prefixed by a capital P, so we set them as<SPAN>&nbsp;</SPAN><CODE>-PversionName</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>-pVersionCode</CODE>. As value, we use two variables provided by Azure DevOps: we're using<SPAN>&nbsp;</SPAN><CODE>$(Build.BuildNumber)</CODE><SPAN>&nbsp;</SPAN>as version name (so something like 2020.1.15), while<SPAN>&nbsp;</SPAN><CODE>$(Build.BuildId)</CODE><SPAN>&nbsp;</SPAN>as version code (which is perfect for our scenario, since it's an incremental integer).</P> </LI> <LI id="pragma-line-307"> <P>The<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file can define one or more tasks, that we can execute when we run the build. In case of a React Native application, we have two options:</P> <UL id="pragma-line-309"> <LI id="pragma-line-309"><CODE>assembleRelease</CODE><SPAN>&nbsp;</SPAN>is used to generate an APK, which can be used for sideloading or distribution through App Center.</LI> <LI id="pragma-line-310"><CODE>bundleRelease</CODE><SPAN>&nbsp;</SPAN>is used to generate an AAP, which is the format required by the Google Play Store.</LI> </UL> <P>In my case I opted for distribution through sideloading (so I need only the APK), but you can also decide to generate both packages simply by setting the<SPAN>&nbsp;</SPAN><CODE>tasks</CODE><SPAN>&nbsp;</SPAN>property like this:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-314" class="language-yaml hljs"><SPAN class="hljs-attr">tasks:</SPAN> <SPAN class="hljs-string">'assembleRelease publishRelease'</SPAN></CODE></PRE> </LI> </OL> <P>&nbsp;</P> <H4 id="sign-the-package">Sign the package</H4> <P>As per the changes we have previously made in the<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file, the generated APK is unsigned. As such, before deploying it, we need to sign it with the certificate we have previously created. We can do it using a dedicated task available in Azure DevOps, which is a much safer approach than doing it as part of the Gradle build: since the task leverages Secure Files and variables, we can protect our private key and our credentials. As first step, we need to upload the<SPAN>&nbsp;</SPAN><STRONG>my-upload-key.keystore</STRONG><SPAN>&nbsp;</SPAN>file we have previously generated as a Secure File on Azure DevOps.<SPAN>&nbsp;</SPAN><A href="#" target="_blank">Secure Files</A><SPAN>&nbsp;</SPAN>is a feature which allows to upload files that can be only used in a specific pipeline or, eventually, deleted. No one, even the administrator of the Azure DevOps account, will be able to download it. It's a perfect fit for our private certificate: even if we're going to share the project with other developers, they won't be able to download the certificate and perform potentially malicious tasks (like using your certificate to sign other applications).</P> <P>&nbsp;</P> <P>To upload the file, move to the<SPAN>&nbsp;</SPAN><STRONG>Library</STRONG><SPAN>&nbsp;</SPAN>section under<SPAN>&nbsp;</SPAN><STRONG>Pipelines</STRONG>. Select the<SPAN>&nbsp;</SPAN><STRONG>Secure Files</STRONG><SPAN>&nbsp;</SPAN>tab and press the + button:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="library.png" style="width: 850px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164062i220DEF837173F016/image-size/large?v=v2&amp;px=999" role="button" title="library.png" alt="library.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Drag the&nbsp;</SPAN><STRONG>my-upload-key.keystore</STRONG><SPAN>&nbsp;file you have previously generated and upload it. That's it. Now we'll be able to reference this file in the signing task. We need to safely save also the store password, the key alias and the key password. We're going to create a variable for each of them, so that we don't have to add them in clear in the task. In the pipeline editor in Azure DevOps click on&nbsp;</SPAN><STRONG>Variables</STRONG><SPAN>&nbsp;and press + to add a new one. Name it as you prefer. For example, let's start with the store password, so call it&nbsp;</SPAN><STRONG>KeyStorePassword</STRONG><SPAN>. As value, type the password you have specified during the key creation, then enable the option&nbsp;</SPAN><STRONG>Keep this value secret</STRONG><SPAN>. This will make sure that no one will be able to read its value.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="StorePassword.png" style="width: 463px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164063iE2137165D59E9161/image-size/large?v=v2&amp;px=999" role="button" title="StorePassword.png" alt="StorePassword.png" /></span></SPAN></P> <P>&nbsp;</P> <P><SPAN>Now repeat the process for the other two variables. At the end, you should see a list like this:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Variables.png" style="width: 467px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164064i94D6E0AFEF83EE9C/image-size/large?v=v2&amp;px=999" role="button" title="Variables.png" alt="Variables.png" /></span></SPAN></P> <P>&nbsp;</P> <P>Now we are ready to add the task to our pipeline:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-338" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">AndroidSigning@3</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> apkFiles:</SPAN> <SPAN class="hljs-string">'**/*.apk'</SPAN> <SPAN class="hljs-attr"> apksignerKeystoreFile:</SPAN> <SPAN class="hljs-string">'my-upload-key.keystore'</SPAN> <SPAN class="hljs-attr"> apksignerKeystorePassword:</SPAN> <SPAN class="hljs-string">'$(KeyStorePassword)'</SPAN> <SPAN class="hljs-attr"> apksignerKeystoreAlias:</SPAN> <SPAN class="hljs-string">'$(KeyAlias)'</SPAN> <SPAN class="hljs-attr"> apksignerKeyPassword:</SPAN> <SPAN class="hljs-string">'$(KeyPassword)'</SPAN> <SPAN class="hljs-attr"> zipalign:</SPAN> <SPAN class="hljs-literal">false</SPAN> </CODE></PRE> <OL id="pragma-line-349"> <LI id="pragma-line-349">Using the<SPAN>&nbsp;</SPAN><CODE>apkFiles</CODE><SPAN>&nbsp;</SPAN>property, we specify we want to sign all the APK files included in the output folder.</LI> <LI id="pragma-line-350">As<SPAN>&nbsp;</SPAN><CODE>apksignerKeystoreFile</CODE><SPAN>&nbsp;</SPAN>we need to add a reference to the private key we have previously uploaded as secure file. We need just to specify it's base name, like if it's a local file.</LI> <LI id="pragma-line-351">As<SPAN>&nbsp;</SPAN><CODE>apksignerKeyStorePassword</CODE>,<SPAN>&nbsp;</SPAN><CODE>apksignerKeystoreAlias</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>apksignerKeyPassword</CODE><SPAN>&nbsp;</SPAN>we need to specify the three variables we have previously created. We use the standard syntax supported by Azure DevOps, which is<SPAN>&nbsp;</SPAN><CODE>$(variableName)</CODE>.</LI> </OL> <H4 id="publish-the-build-artifacts">Publish the build artifacts</H4> <P>We're almost done. We just need to publish the signed APK package as build artifact, so that our CI pipeline will be able to pick it for deployment. This is the task to add:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-356" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">PublishBuildArtifacts@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> PathtoPublish:</SPAN> <SPAN class="hljs-string">'android/app/build/outputs/apk/release'</SPAN> <SPAN class="hljs-attr"> ArtifactName:</SPAN> <SPAN class="hljs-string">'drop'</SPAN> <SPAN class="hljs-attr"> publishLocation:</SPAN> <SPAN class="hljs-string">'Container'</SPAN> </CODE></PRE> <P>When you generate an APK for a React Native application, the file is created inside the<SPAN>&nbsp;</SPAN><STRONG>android/app/build/outputs/apk/release</STRONG><SPAN>&nbsp;</SPAN>folder. As such, we copy only the content of this folder. In case you have generated also an AAP for the Google Play Store, the file will be inside the<SPAN>&nbsp;</SPAN><STRONG>android/app/build/outputs/bundle/release</STRONG><SPAN>&nbsp;</SPAN>folder.</P> <P>&nbsp;</P> <H3 id="executing-the-pipeline">Executing the pipeline</H3> <P>After all the changes you have done, this is how the final pipeline should look like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-369" class="language-yaml hljs"><SPAN class="hljs-attr">trigger:</SPAN> <SPAN class="hljs-bullet">-</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-attr">pool:</SPAN> <SPAN class="hljs-attr"> vmImage:</SPAN> <SPAN class="hljs-string">'ubuntu-latest'</SPAN> <SPAN class="hljs-attr">name:</SPAN> <SPAN class="hljs-string">$(date:yyyy).$(Month)$(rev:.r)</SPAN> <SPAN class="hljs-attr">steps:</SPAN> <SPAN class="hljs-attr">- script:</SPAN> <SPAN class="hljs-string">yarn</SPAN> <SPAN class="hljs-string">install</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">Gradle@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> gradleWrapperFile:</SPAN> <SPAN class="hljs-string">'android/gradlew'</SPAN> <SPAN class="hljs-attr"> workingDirectory:</SPAN> <SPAN class="hljs-string">'android/'</SPAN> <SPAN class="hljs-attr"> options:</SPAN> <SPAN class="hljs-string">'-PversionName=$(Build.BuildNumber) -PversionCode=$(Build.BuildId)'</SPAN> <SPAN class="hljs-attr"> tasks:</SPAN> <SPAN class="hljs-string">'assembleRelease'</SPAN> <SPAN class="hljs-attr"> publishJUnitResults:</SPAN> <SPAN class="hljs-literal">false</SPAN> <SPAN class="hljs-attr"> javaHomeOption:</SPAN> <SPAN class="hljs-string">'JDKVersion'</SPAN> <SPAN class="hljs-attr"> gradleOptions:</SPAN> <SPAN class="hljs-string">'-Xmx3072m'</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">AndroidSigning@3</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> apkFiles:</SPAN> <SPAN class="hljs-string">'**/*.apk'</SPAN> <SPAN class="hljs-attr"> apksignerKeystoreFile:</SPAN> <SPAN class="hljs-string">'my-upload-key.keystore'</SPAN> <SPAN class="hljs-attr"> apksignerKeystorePassword:</SPAN> <SPAN class="hljs-string">'$(KeyStorePassword)'</SPAN> <SPAN class="hljs-attr"> apksignerKeystoreAlias:</SPAN> <SPAN class="hljs-string">'$(KeyAlias)'</SPAN> <SPAN class="hljs-attr"> apksignerKeyPassword:</SPAN> <SPAN class="hljs-string">'$(KeyPassword)'</SPAN> <SPAN class="hljs-attr"> zipalign:</SPAN> <SPAN class="hljs-literal">false</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">PublishBuildArtifacts@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> PathtoPublish:</SPAN> <SPAN class="hljs-string">'android/app/build/outputs/apk/release'</SPAN> <SPAN class="hljs-attr"> ArtifactName:</SPAN> <SPAN class="hljs-string">'drop'</SPAN> <SPAN class="hljs-attr"> publishLocation:</SPAN> <SPAN class="hljs-string">'Container'</SPAN> </CODE></PRE> <P>After saving it, Azure DevOps will automatically trigger it. Checking the logs will be helpful to understand if everything is running fine. For example, the version code and version number we have passed as parameters should be displayed as part of the Gradle task, since we have printed them:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GradleLogging.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164065iF61C9DA527076E71/image-size/large?v=v2&amp;px=999" role="button" title="GradleLogging.png" alt="GradleLogging.png" /></span></P> <P>&nbsp;</P> <P><SPAN>At the end of the process, your artifact will contain the generated APK:</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="GenericAPK.png" style="width: 769px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164066iA38D092BB001EC16/image-size/large?v=v2&amp;px=999" role="button" title="GenericAPK.png" alt="GenericAPK.png" /></span></SPAN></P> <P>&nbsp;</P> <H4 id="improving-the-generated-name">Improving the generated file name</H4> <P>By default, the generated APK will be called<SPAN>&nbsp;</SPAN><STRONG>app-release-unsigned.apk</STRONG>. There isn't any specific downside in using this name, but it may be hard to track which exact app and version is included inside the package. It would be better to give it a more meaningful name, maybe including also the version number. We can do it by modifying the<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file included in the<SPAN>&nbsp;</SPAN><STRONG>android/app</STRONG><SPAN>&nbsp;</SPAN>folder. Towards the end of the file you will find a section called<SPAN>&nbsp;</SPAN><CODE>applicationVariants.all</CODE>, which iterates all the files which are generated by the build process to configure them. This is how it looks like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-419" class="language-yaml hljs"><SPAN class="hljs-string">applicationVariants.all</SPAN> <SPAN class="hljs-string">{</SPAN> <SPAN class="hljs-string">variant</SPAN> <SPAN class="hljs-bullet">-&gt;</SPAN> <SPAN class="hljs-string">variant.outputs.each</SPAN> <SPAN class="hljs-string">{</SPAN> <SPAN class="hljs-string">output</SPAN> <SPAN class="hljs-bullet">-&gt;</SPAN> <SPAN class="hljs-string">//</SPAN> <SPAN class="hljs-string">For</SPAN> <SPAN class="hljs-string">each</SPAN> <SPAN class="hljs-string">separate</SPAN> <SPAN class="hljs-string">APK</SPAN> <SPAN class="hljs-string">per</SPAN> <SPAN class="hljs-string">architecture,</SPAN> <SPAN class="hljs-string">set</SPAN> <SPAN class="hljs-string">a</SPAN> <SPAN class="hljs-string">unique</SPAN> <SPAN class="hljs-string">version</SPAN> <SPAN class="hljs-string">code</SPAN> <SPAN class="hljs-string">as</SPAN> <SPAN class="hljs-string">described</SPAN> <SPAN class="hljs-attr">here:</SPAN> <SPAN class="hljs-string">//</SPAN> <SPAN class="hljs-attr">https://developer.android.com/studio/build/configure-apk-splits.html</SPAN> <SPAN class="hljs-string">def</SPAN> <SPAN class="hljs-string">versionCodes</SPAN> <SPAN class="hljs-string">=</SPAN> <SPAN class="hljs-string">["armeabi-v7a":</SPAN> <SPAN class="hljs-number">1</SPAN><SPAN class="hljs-string">,</SPAN> <SPAN class="hljs-string">"x86"</SPAN><SPAN class="hljs-string">:</SPAN> <SPAN class="hljs-number">2</SPAN><SPAN class="hljs-string">,</SPAN> <SPAN class="hljs-string">"arm64-v8a"</SPAN><SPAN class="hljs-string">:</SPAN> <SPAN class="hljs-number">3</SPAN><SPAN class="hljs-string">,</SPAN> <SPAN class="hljs-string">"x86_64"</SPAN><SPAN class="hljs-string">:</SPAN> <SPAN class="hljs-number">4</SPAN><SPAN class="hljs-string">]</SPAN> <SPAN class="hljs-string">def</SPAN> <SPAN class="hljs-string">abi</SPAN> <SPAN class="hljs-string">=</SPAN> <SPAN class="hljs-string">output.getFilter(OutputFile.ABI)</SPAN> <SPAN class="hljs-string">if</SPAN> <SPAN class="hljs-string">(abi</SPAN> <SPAN class="hljs-string">!=</SPAN> <SPAN class="hljs-literal">null</SPAN><SPAN class="hljs-string">)</SPAN> <SPAN class="hljs-string">{</SPAN> <SPAN class="hljs-string">//</SPAN> <SPAN class="hljs-literal">null</SPAN> <SPAN class="hljs-string">for</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">universal-debug,</SPAN> <SPAN class="hljs-string">universal-release</SPAN> <SPAN class="hljs-string">variants</SPAN> <SPAN class="hljs-string">output.versionCodeOverride</SPAN> <SPAN class="hljs-string">=</SPAN> <SPAN class="hljs-string">versionCodes.get(abi)</SPAN> <SPAN class="hljs-string">*</SPAN> <SPAN class="hljs-number">1048576</SPAN> <SPAN class="hljs-string">+</SPAN> <SPAN class="hljs-string">defaultConfig.versionCode</SPAN> <SPAN class="hljs-string">}</SPAN> <SPAN class="hljs-string">}</SPAN> <SPAN class="hljs-string">}</SPAN> </CODE></PRE> <P>The<SPAN>&nbsp;</SPAN><CODE>output</CODE><SPAN>&nbsp;</SPAN>object represents a single file that is produced by the build, so we can use it to change the file name thanks to the<SPAN>&nbsp;</SPAN><CODE>outputFileName</CODE><SPAN>&nbsp;</SPAN>property. This is how the section should look like after our change:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-436" class="language-yaml hljs"><SPAN class="hljs-string">applicationVariants.all</SPAN> <SPAN class="hljs-string">{</SPAN> <SPAN class="hljs-string">variant</SPAN> <SPAN class="hljs-bullet">-&gt;</SPAN> <SPAN class="hljs-string">variant.outputs.each</SPAN> <SPAN class="hljs-string">{</SPAN> <SPAN class="hljs-string">output</SPAN> <SPAN class="hljs-bullet">-&gt;</SPAN> <SPAN class="hljs-string">//</SPAN> <SPAN class="hljs-string">For</SPAN> <SPAN class="hljs-string">each</SPAN> <SPAN class="hljs-string">separate</SPAN> <SPAN class="hljs-string">APK</SPAN> <SPAN class="hljs-string">per</SPAN> <SPAN class="hljs-string">architecture,</SPAN> <SPAN class="hljs-string">set</SPAN> <SPAN class="hljs-string">a</SPAN> <SPAN class="hljs-string">unique</SPAN> <SPAN class="hljs-string">version</SPAN> <SPAN class="hljs-string">code</SPAN> <SPAN class="hljs-string">as</SPAN> <SPAN class="hljs-string">described</SPAN> <SPAN class="hljs-attr">here:</SPAN> <SPAN class="hljs-string">//</SPAN> <SPAN class="hljs-attr">https://developer.android.com/studio/build/configure-apk-splits.html</SPAN> <SPAN class="hljs-string">def</SPAN> <SPAN class="hljs-string">versionCodes</SPAN> <SPAN class="hljs-string">=</SPAN> <SPAN class="hljs-string">["armeabi-v7a":</SPAN> <SPAN class="hljs-number">1</SPAN><SPAN class="hljs-string">,</SPAN> <SPAN class="hljs-string">"x86"</SPAN><SPAN class="hljs-string">:</SPAN> <SPAN class="hljs-number">2</SPAN><SPAN class="hljs-string">,</SPAN> <SPAN class="hljs-string">"arm64-v8a"</SPAN><SPAN class="hljs-string">:</SPAN> <SPAN class="hljs-number">3</SPAN><SPAN class="hljs-string">,</SPAN> <SPAN class="hljs-string">"x86_64"</SPAN><SPAN class="hljs-string">:</SPAN> <SPAN class="hljs-number">4</SPAN><SPAN class="hljs-string">]</SPAN> <SPAN class="hljs-string">def</SPAN> <SPAN class="hljs-string">abi</SPAN> <SPAN class="hljs-string">=</SPAN> <SPAN class="hljs-string">output.getFilter(OutputFile.ABI)</SPAN> <SPAN class="hljs-string">if</SPAN> <SPAN class="hljs-string">(abi</SPAN> <SPAN class="hljs-string">!=</SPAN> <SPAN class="hljs-literal">null</SPAN><SPAN class="hljs-string">)</SPAN> <SPAN class="hljs-string">{</SPAN> <SPAN class="hljs-string">//</SPAN> <SPAN class="hljs-literal">null</SPAN> <SPAN class="hljs-string">for</SPAN> <SPAN class="hljs-string">the</SPAN> <SPAN class="hljs-string">universal-debug,</SPAN> <SPAN class="hljs-string">universal-release</SPAN> <SPAN class="hljs-string">variants</SPAN> <SPAN class="hljs-string">output.versionCodeOverride</SPAN> <SPAN class="hljs-string">=</SPAN> <SPAN class="hljs-string">versionCodes.get(abi)</SPAN> <SPAN class="hljs-string">*</SPAN> <SPAN class="hljs-number">1048576</SPAN> <SPAN class="hljs-string">+</SPAN> <SPAN class="hljs-string">defaultConfig.versionCode</SPAN> <SPAN class="hljs-string">}</SPAN> <SPAN class="hljs-string">output.outputFileName</SPAN> <SPAN class="hljs-string">=</SPAN> <SPAN class="hljs-string">output.outputFileName</SPAN> <SPAN class="hljs-string">.replace("app-",</SPAN> <SPAN class="hljs-string">"NavigationSample-"</SPAN><SPAN class="hljs-string">)</SPAN> <SPAN class="hljs-string">.replace("unsigned",</SPAN> <SPAN class="hljs-string">""</SPAN><SPAN class="hljs-string">)</SPAN> <SPAN class="hljs-string">.replace(".apk",</SPAN> <SPAN class="hljs-string">"${variant.versionName}.apk"</SPAN><SPAN class="hljs-string">)</SPAN> <SPAN class="hljs-string">}</SPAN> <SPAN class="hljs-string">}</SPAN> </CODE></PRE> <P>We're taking the<SPAN>&nbsp;</SPAN><CODE>outputFileName</CODE><SPAN>&nbsp;</SPAN>property and:</P> <UL id="pragma-line-458"> <LI id="pragma-line-458">We replace the generic<SPAN>&nbsp;</SPAN><CODE>app-</CODE><SPAN>&nbsp;</SPAN>string with the name of our application (in my case, it's<SPAN>&nbsp;</SPAN><CODE>NavigationSample-</CODE>).</LI> <LI id="pragma-line-459">We remove the<SPAN>&nbsp;</SPAN><CODE>unsigned</CODE><SPAN>&nbsp;</SPAN>string</LI> <LI id="pragma-line-460">We add, as suffix, the version number, which can be referenced with the<SPAN>&nbsp;</SPAN><CODE>${variant.versionName}</CODE><SPAN>&nbsp;</SPAN>property.</LI> </UL> <P>Now, if we save the<SPAN>&nbsp;</SPAN><STRONG>build.gradle</STRONG><SPAN>&nbsp;</SPAN>file and we push it to our repository, Azure DevOps will trigger a new execution of the pipeline. At the end of the process, we will find in the artifacts an APK with a more meaningful name, like<SPAN>&nbsp;</SPAN><STRONG>NavigationSample-2020.1.18.apk</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RealNameAPK.png" style="width: 751px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/164067i4FDD52ABB800C21E/image-size/large?v=v2&amp;px=999" role="button" title="RealNameAPK.png" alt="RealNameAPK.png" /></span></P> <P>&nbsp;</P> <H3 id="deploy-the-application">Deploy the application</H3> <P>Now that you have generated a signed package, you can create a release pipeline to do the deployment. You have many options, based on your scenario. If you have generated an AAP and you want to publish it on the Google Play Store, Azure DevOps<SPAN>&nbsp;</SPAN><A href="#" target="_blank">offers a task for that</A>. Another great option is to deploy your application to a group of testers, that will help you to validate the update before publishing it on the Store. In this case you can leverage App Center, the Microsoft platform to automate the build, deployment and testing of mobile and desktop apps. Also for this scenario Azure DevOps<SPAN>&nbsp;</SPAN><A href="#" target="_blank">offers a dedicated task</A>, you'll just need a free account on<SPAN>&nbsp;</SPAN><A href="#" target="_blank">App Center</A>.</P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>In this post we have learned how to create a CI pipeline for our React Native project to generate an Android application. We have created it alongside the Windows one we have already created<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_blank">in another post</A>. Now, every time we're going to commit some new code to our repository, Azure DevOps will automatically build and deploy both the Windows and Android versions of our application. Once they have been validated, we can automatically deploy them to our users, regardless if we're using sideloading or the Store.</P> <P>&nbsp;</P> <P>If you're building React Native applications for Android and iOS, there's also another option you can explore: App Center. Other than using it to perform the deployment,<SPAN>&nbsp;</SPAN><A href="#" target="_blank">App Center supports also directly enabling CI/CD</A>. You just need to connect it to your repository, specify that your project is based on React Native and setup a few parameters (like providing the private certificate and the passwords). App Center will take care of everything and it will automatically create a pipeline for you. In my case, I preferred to use Azure DevOps because I wanted more flexibility and to consolidate all the pipelines for my project (Windows, Android and in the future iOS) in a single place. However, App Center is a great choice if you're looking for a simple and reliable solution to automatically build and deploy your React Native projects.</P> <P>&nbsp;</P> <P>Happy coding!</P> Tue, 07 Jan 2020 13:13:36 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/using-azure-devops-to-create-a-ci-cd-pipeline-for-an-android/ba-p/1094422 Matteo Pagani 2020-01-07T13:13:36Z React Native for Windows and native modules: how to add CI/CD to your project https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/react-native-for-windows-and-native-modules-how-to-add-ci-cd-to/ba-p/1085473 <P><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_blank" rel="noopener">In the previous post</A><SPAN>&nbsp;</SPAN>we have learned how to create a CI/CD pipeline on Azure DevOps to automatically build and deploy a Windows application built with React Native. Everything worked fine but, in that scenario, I used as a sample the basic React Native template, which doesn't leverage any native module. Especially when you're building a real application, however, it's a very uncommon scenario. At some point, you will have to interact with any of the native features offered by the platform, even if it's a simple one like the file system. For this reason,<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/Windows-Dev-AppConsult/Building-a-React-Native-module-for-Windows/ba-p/1067893" target="_blank" rel="noopener">in another blog post I wrote</A>, we have learned how we can build native modules for Windows, so that your React Native application can access to native Windows features using JavaScript.</P> <P>&nbsp;</P> <P>However, as soon as you try to setup a CI/CD pipeline<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_blank" rel="noopener">following the guidance of the previous post</A><SPAN>&nbsp;</SPAN>for your React Native project which uses native modules for Windows, you hit a blocker. The Visual Studio build task will fail with a series of errors like the following ones:</P> <P>&nbsp;</P> <PRE><CODE class="language-plaintext hljs">[error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\AttributedViewManager.cs(447,21): Error CS8107: Feature 'default literal' is not available in C# 7.0. Please use language version 7.1 or greater. [error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\JSValue.cs(105,36): Error CS8107: Feature 'readonly references' is not available in C# 7.0. Please use language version 7.2 or greater. </CODE></PRE> <P>The reason is that, when you build a native module in C#, you reference a project included in the React Native for Windows implementation called<SPAN>&nbsp;</SPAN><STRONG>Microsoft.ReactNative.SharedManaged</STRONG>, which provides various attributes to expose C# classes and methods to JavaScript. This project leverages many features of the C# language that has been added after the 7.0 release, which aren't supported by Visual Studio 2017, that is the version installed on our hosted agent. We need to switch to Visual Studio 2019 but we can't just do that: the React Native for Windows implementation leverages many C++ projects which target the v141 C++ platform toolset, which is installed by default in Visual Studio 2017 but not in Visual Studio 2019.</P> <P>&nbsp;</P> <P>Shall we give up in our plan of using Azure DevOps to build a CI/CD pipeline for this scenario? Absolutely not! Self-hosted agents to the rescue =)</img></P> <P>&nbsp;</P> <H3 id="setting-up-the-self-hosted-agent">Setting up the self-hosted agent</H3> <P>For scenarios like this, where the available hosted agents don't meet our requirements, Azure DevOps supports the concept of<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">self-hosted agents</A>. A self-hosted agent is nothing more than a regular machine, where you deploy an agent which is able to receive tasks from Azure DevOps and execute them. The agent is an application that can run in interactive mode or simply be installed as a service, so that it can performs all the operations in background. The advantage of a self-hosted agent is its flexibility: being a regular machine, you can install everything you need to satisfy your requirements: development tools, SDKs, frameworks, etc. This helps also to reduce the execution times of a pipeline. Sometimes, some of our requirements can be satisfied also on hosted agents with installation tasks: for example, in the previous post we have learned how to use Chocolatey to install the Windows 10 1903 SDK, which is missing on the<SPAN>&nbsp;</SPAN><STRONG>vs2017-win2016</STRONG><SPAN>&nbsp;</SPAN>agent. However, all these tasks must be added on top of the compilation time, making the whole execution longer.</P> <P>&nbsp;</P> <P>The downside of a self-hosted agent is that you need to maintain it. You need to keep it up &amp; running, you need to patch it, you need to pay for it, etc. Hosted agents, instead, are maintained directly by Microsoft. The difference between a hosted agent and a self-hosted agent is not very different between choosing a IaaS (Infrastructure as a Service) or PaaS (Platform as a Service) approach for running an application in the cloud.</P> <P>&nbsp;</P> <P>Setting up a self-hosted agent is a quite straightforward operation. First, you need to have a machine that you want to use as an agent. It can be any kind of machine: physical, virtual, in the cloud. As long as it's connected to Internet, you're good to go. Of course, the best approach is to use a dedicated virtual machine, where you're going to install only the tools you need to perform the compilation. In my case, I opted for creating a dedicated VM on Azure. If you have a Visual Studio subscription linked to your Azure account, you can choose one of the available Windows 10 Pro images (in my case, I chose the most recent version, Windows 10 1909). Otherwise, you can choose a Windows Server 2019 image as well.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Windows101909.png" style="width: 455px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163520iC6A37F0F28D1FDA0/image-size/large?v=v2&amp;px=999" role="button" title="Windows101909.png" alt="Windows101909.png" /></span></P> <P>As size, keep in mind that disk speed is very important. React Native for Windows is built on top of C++ and, as such, the compilation times are quite long. If you choose a cheap storage (like a regular HDD instead of SSD), the compilation time will be very long. My suggestion is to go, at least, with a<SPAN>&nbsp;</SPAN><STRONG>DS2_V2</STRONG>, which is the same size leveraged by the Microsoft hosted agents.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="VMsize.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163521iA4C4EB75210D9D20/image-size/large?v=v2&amp;px=999" role="button" title="VMsize.png" alt="VMsize.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Once your VM is up &amp; running, regardless if you have created it locally or in the cloud, you will need to setup the agent.</SPAN></P> <P>&nbsp;</P> <H3 id="configuring-the-agent">Configuring the agent</H3> <P>I won't go into all the details on how to setup a self-hosted agent, since my colleague Freist has explained it in a great way<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/Windows-Dev-AppConsult/DevOps-for-Desktop-How-to-use-Self-Hosted-agent-for-Win10-UWP/ba-p/774553" target="_blank" rel="noopener">in the following blog post</A>. Also<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">the official documentation</A><SPAN>&nbsp;</SPAN>does a great job in explaining all the required steps.</P> <P>&nbsp;</P> <P>The process is really easy. You just need to download a package from the Azure DevOps website, copy it over on your machine and run a script to configure it. The script will ask you a few questions, like the security token to connect your Azure DevOps instance, the work folder, if you want to install the agent as a Windows Service, etc. Once the operation is complete, you're all set. The agent will take care of communicating with Azure DevOps via HTTPS, to exchange information: it will receive the tasks to perform and it will send back the produced artifacts.</P> <P>&nbsp;</P> <P>Once the agent is up &amp; running, you can start installing all the tools you need to compile our React Native for Windows project. Let's see them.</P> <P>&nbsp;</P> <H4 id="visual-studio-2019">Visual Studio 2019</H4> <P>Any version,<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">including the Community one</A>, will be fine. Just make sure to install the workloads and components required by React Native, which are described<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">in the official documentation</A>:</P> <UL> <LI>Workloads <UL> <LI>Universal Windows Platform development <UL> <LI>Enable the optional C++ (v141) Universal Windows Platform tools</LI> </UL> </LI> <LI>Desktop development with C++</LI> </UL> </LI> <LI>Individual Components <UL> <LI>Compilers, build tools and runtimes <UL> <LI>VC v141 - VS 2017 C++ x64/x86 build tools (v14.16)</LI> <LI>MSVC v141 - VS 2017 C++ ARM build tools (v14.16)</LI> </UL> </LI> </UL> </LI> </UL> <P>&nbsp;</P> <H4 id="chocolatey">Chocolatey</H4> <P>We have already talked about<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Chocolatey</A><SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_blank" rel="noopener">in the previous post</A>. It's a popular package manager for Windows and it's the easiest way to install software on a machine (especially a build agent), since everything can be done via command-line, without requiring any user interaction. To install it, you just need to open an administrative PowerShell prompt on the VM (the easiest way is to right click on the Start button and choose<SPAN>&nbsp;</SPAN><STRONG>Windows PowerShell (Admin)</STRONG>) and launch the following command:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs"><SPAN class="hljs-built_in">Set-ExecutionPolicy</SPAN> Bypass -Scope <SPAN class="hljs-keyword">Process</SPAN> -Force; iex ((<SPAN class="hljs-built_in">New-Object</SPAN> System.Net.WebClient).DownloadString(<SPAN class="hljs-string">'https://chocolatey.org/install.ps1'</SPAN>)) </CODE></PRE> <P>Once the tool is installed, you'll be able to install applications just by launching<SPAN>&nbsp;</SPAN><CODE>choco install</CODE><SPAN>&nbsp;</SPAN>followed by the unique identifier of the package. You can explore all the available packages<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on the official website</A>.</P> <P>&nbsp;</P> <H4 id="nodejs">Node.js</H4> <P>You will need Node.js installed on the machine, since it's leveraged by React Native to handle all the modules. Now that we have Chocolatey on the machine, installing Node.js is really easy. Open a PowerShell prompt with administrative rights and run the following command:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">choco install nodejs.install --version=<SPAN class="hljs-number">12.9</SPAN>.<SPAN class="hljs-number">1</SPAN> </CODE></PRE> <P>Make sure to specify the<SPAN>&nbsp;</SPAN><CODE>--version</CODE><SPAN>&nbsp;</SPAN>property, since 12.9.1 is the Node.js version which works best with React Native at the time of writing. Different versions can lead to errors during the bundle generation.</P> <P>&nbsp;</P> <H4 id="yarn">Yarn</H4> <P>When you commit your React Native project to your repository, by default the<SPAN>&nbsp;</SPAN><STRONG>node_modules</STRONG><SPAN>&nbsp;</SPAN>folder is ignored. These are libraries and tools which haven't been developed by us, so it wouldn't make sense to waste space on our repository for stuff that can be easily downloaded. As such, you will have to restore all the dependencies before running the compilation. The best way to do is to leverage<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Yarn</A>, a popular package manager for Node. Also Yarn is available on Chocolatey, so you can just run the following command from a PowerShell prompt with administrative rights:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">choco install yarn</CODE></PRE> <P>&nbsp;</P> <H4 id="react-native-cli">React Native CLI</H4> <P>During the compilation of the package, at some point Visual Studio will launch the<SPAN>&nbsp;</SPAN><CODE>react-native bundle</CODE><SPAN>&nbsp;</SPAN>command, which bundles all the JavaScript files together so that you don't need the Metro packager up &amp; running to launch the application. In order to achieve this task, you will need to install first the<SPAN>&nbsp;</SPAN><STRONG>react-native-cli</STRONG><SPAN>&nbsp;</SPAN>package globally. However, there's a catch, which we have learned about also<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_blank" rel="noopener">in the previous post</A>. The React Native CLI must be available to the user which runs the build agent service, otherwise Visual Studio won't be able to find it. In case of a hosted agent, we learned that the user that runs this service is called<SPAN>&nbsp;</SPAN><STRONG>VssAdministrator</STRONG>. What about self-hosted agents? If you have installed the agent as a service using the default options, the service will be running using the<SPAN>&nbsp;</SPAN><STRONG>Network Service</STRONG><SPAN>&nbsp;</SPAN>user or the<SPAN>&nbsp;</SPAN><STRONG>Local System</STRONG><SPAN>&nbsp;</SPAN>account, which are special users that doesn't have a dedicated account. As such, you have two options:</P> <OL> <LI>You can create on the machine a dedicated user with administrator rights, which will be dedicated to the agent service.</LI> <LI>You can use your current default user to run the agent service.</LI> </OL> <P>Regardless of your choice, to change this behavior open the Start menu and type:</P> <P>&nbsp;</P> <PRE><CODE class="language-plaintext hljs">services.msc </CODE></PRE> <P>You will open a window that will list all the services installed in the system. Look for the agent service, which will be called<SPAN>&nbsp;</SPAN><STRONG>Azure Pipelines Agent</STRONG>, followed by the a string composed by:</P> <UL> <LI>The name of your Azure DevOps account</LI> <LI>The name of the pool the agent belongs to</LI> <LI>The name of the machine</LI> </UL> <P>For example, in my case it's called<SPAN>&nbsp;</SPAN><STRONG>Azure Pipelines Agent (mpagani-ms.Default.VSPreviewBuild)</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AgentName.png" style="width: 957px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163522i3E1CA7F7626B00AC/image-size/large?v=v2&amp;px=999" role="button" title="AgentName.png" alt="AgentName.png" /></span></P> <P>&nbsp;</P> <P><SPAN>Now double click on it to open its properties and move to the&nbsp;</SPAN><STRONG>Log on</STRONG><SPAN>&nbsp;section. Select the&nbsp;</SPAN><STRONG>This account</STRONG><SPAN>&nbsp;option and, with the&nbsp;</SPAN><STRONG>Browse</STRONG><SPAN>&nbsp;button, search for the user on the machine you have decide to use to run the service. In my case, I chose to leverage my current user, which is called&nbsp;</SPAN><STRONG>qmatteoq</STRONG><SPAN>&nbsp;and he has administrator rights. You will need to provide also the password of the account.</SPAN></P> <P>&nbsp;</P> <P><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ServiceUser.png" style="width: 418px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163523i957BA0B7134939E5/image-size/large?v=v2&amp;px=999" role="button" title="ServiceUser.png" alt="ServiceUser.png" /></span></SPAN></P> <P>&nbsp;</P> <P>Once you have setup the user who runs the agent, you're ready to install the React Native CLI. Open an administrative prompt and run first the following command:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">npm config set prefix C:\Users\qmatteoq\AppData\Roaming\npm </CODE></PRE> <P>Make sure to replace<SPAN>&nbsp;</SPAN><STRONG>qmatteoq</STRONG><SPAN>&nbsp;</SPAN>with the name of the user you have chosen to run the service. This command will set the default folder which will be used as cache for NPM packages. Now you can install the CLI with the following command:</P> <P>&nbsp;</P> <PRE><CODE class="language-powershell hljs">npm install -g react-native-cli </CODE></PRE> <P>That's it. This was the last requirement to install on the machine. Now we can go back to the Azure DevOps portal and create our pipeline.</P> <P>&nbsp;</P> <H3 id="setup-the-cicd-pipeline">Setup the CI/CD pipeline</H3> <P>The process of creating a CI/CD pipeline isn't very different from the one we did in the previous post. In your Azure DevOps project move to the Pipelines section, create a new pipeline, choose where your source code is hosted and, in the end, choose<SPAN>&nbsp;</SPAN><STRONG>Universal Windows Platform</STRONG><SPAN>&nbsp;</SPAN>as last template. We will need, also in this case, to make a few tweaks, but we will have to perform fewer steps.<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253" target="_blank" rel="noopener">In the previous post</A>, in fact, we were leveraging a hosted agent, so we needed to install every time the missing dependencies, like the Windows 10 1903 SDK or the React Native CLI. In our scenario, instead, we have already installed all the dependencies on the machine, so we don't need to install them every time we perform a new build.</P> <P>Here is how your YAML file should look like:</P> <P>&nbsp;</P> <PRE><CODE class="language-yaml hljs"><SPAN class="hljs-comment"># Universal Windows Platform</SPAN> <SPAN class="hljs-comment"># Build a Universal Windows Platform project using Visual Studio.</SPAN> <SPAN class="hljs-comment"># Add steps that test and distribute an app, save build artifacts, and more:</SPAN> <SPAN class="hljs-comment"># https://aka.ms/yaml</SPAN> <SPAN class="hljs-attr">trigger:</SPAN> <SPAN class="hljs-bullet">-</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-attr">pool:</SPAN> <SPAN class="hljs-string">Default</SPAN> <SPAN class="hljs-attr">variables:</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'windows/*.sln'</SPAN> <SPAN class="hljs-attr"> buildPlatform:</SPAN> <SPAN class="hljs-string">'x64'</SPAN> <SPAN class="hljs-attr"> buildConfiguration:</SPAN> <SPAN class="hljs-string">'Release'</SPAN> <SPAN class="hljs-attr"> appxPackageDir:</SPAN> <SPAN class="hljs-string">'$(build.artifactStagingDirectory)\AppxPackages\\'</SPAN> <SPAN class="hljs-attr">name:</SPAN> <SPAN class="hljs-string">$(date:yyyy).$(Month)$(rev:.r).0</SPAN> <SPAN class="hljs-attr">steps:</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">VersionAPPX@2</SPAN> <SPAN class="hljs-attr"> displayName:</SPAN> <SPAN class="hljs-string">'Version MSIX'</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> Path:</SPAN> <SPAN class="hljs-string">'$(Build.SourcesDirectory)'</SPAN> <SPAN class="hljs-attr"> VersionNumber:</SPAN> <SPAN class="hljs-string">'$(Build.BuildNumber)'</SPAN> <SPAN class="hljs-attr"> InjectVersion:</SPAN> <SPAN class="hljs-literal">true</SPAN> <SPAN class="hljs-attr">- script:</SPAN> <SPAN class="hljs-string">yarn</SPAN> <SPAN class="hljs-string">install</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">NuGetCommand@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> command:</SPAN> <SPAN class="hljs-string">'restore'</SPAN> <SPAN class="hljs-attr"> restoreSolution:</SPAN> <SPAN class="hljs-string">'windows/*.sln'</SPAN> <SPAN class="hljs-attr"> feedsToUse:</SPAN> <SPAN class="hljs-string">'select'</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">VSBuild@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'$(solution)'</SPAN> <SPAN class="hljs-attr"> msbuildArgs:</SPAN> <SPAN class="hljs-string">'/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Never /p:UapAppxPackageBuildMode=SideloadOnly /p:AppxPackageSigningEnabled=false'</SPAN> <SPAN class="hljs-attr"> platform:</SPAN> <SPAN class="hljs-string">'$(buildPlatform)'</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">'$(buildConfiguration)'</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">PublishBuildArtifacts@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> PathtoPublish:</SPAN> <SPAN class="hljs-string">'$(appxPackageDir)'</SPAN> <SPAN class="hljs-attr"> ArtifactName:</SPAN> <SPAN class="hljs-string">'drop'</SPAN> </CODE></PRE> <P>Let's see which are the major differences compared to the one we have created in the previous post:</P> <UL> <LI>We have set the<SPAN>&nbsp;</SPAN><CODE>pool</CODE><SPAN>&nbsp;</SPAN>property to<SPAN>&nbsp;</SPAN><CODE>Default</CODE>. If you have setup the agent on the machine using the default settings, it will belong to the Default pool. With this setting we're telling to Azure DevOps that we don't want to use a hosted agent, but our custom one.</LI> <LI>We don't need anymore to install Yarn, the Windows 1903 SDK and the React Native CLI at every build. We have already installed them when we have configured the machine.</LI> </UL> <P>The rest of the steps are the same:</P> <OL> <LI>We change the build number, so that it's compliant with the rules required by a MSIX manifest to define a version number (<STRONG>x.y.z.0</STRONG>). Then, using the<SPAN>&nbsp;</SPAN><CODE>VersionAPPX</CODE><SPAN>&nbsp;</SPAN>task, we inject it in the manifest of your application.</LI> <LI>We restore all the Node dependencies with the<SPAN>&nbsp;</SPAN><CODE>yarn install</CODE><SPAN>&nbsp;</SPAN>command.</LI> <LI>We restore the NuGet packages required by React Native for Windows.</LI> <LI>We build the project using Visual Studio.</LI> <LI>We publish the build artifacts on Azure DevOps, so that other pipelines (like a release pipeline) can pick it to perform additional tasks, like performing a deployment.</LI> </OL> <P>&nbsp;</P> <H3 id="thats-all-folks">That's all folks!</H3> <P>That's it! Now we have a CI pipeline that can automatically build a new MSIX package every time we commit some code to the repository of our React Native project which uses one or more native modules for Windows. Now we can build a release pipeline, which will take care of deploying the MSIX package we have just created to our users: we can upload it on the Microsoft Store, we can upload it on a website or cloud storage to make it available through sideloading, etc. You can learn more about this in the last chapter of my latest e-book titled<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">MSIX Succinctly</A>, which has been published and released for free by Syncfusion, or in Exercise 6 of the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Windows application modernization workshop</A><SPAN>&nbsp;</SPAN>built by my team.</P> <P>&nbsp;</P> <P>Happy coding!</P> Thu, 02 Jan 2020 09:25:44 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/react-native-for-windows-and-native-modules-how-to-add-ci-cd-to/ba-p/1085473 Matteo Pagani 2020-01-02T09:25:44Z Create a CI/CD pipeline for a React Native for Windows application https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253 <P><STRONG>Update on 2nd January 2020</STRONG></P> <P>The <A href="#" target="_blank" rel="noopener">bug</A> described in the post which prevented the Bundle folder to be included in the final AppX / MISX package <A href="#" target="_blank" rel="noopener">has been fixed</A>. As such, the step in the pipeline to workaround the bug has been removed, since it isn't needed anymore.</P> <P>&nbsp;</P> <P>After working for a while on React Native for Windows, I've started pretty soon to research how to build a CI/CD pipeline for such a project. I'm a big fan of Azure DevOps and having an automated cycle where I can continuously deliver new versions of my application just by committing new code to my repository is priceless for me. Now it's a good time to start playing with CI/CD for React Native for Windows: as I have mentioned at the end of<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/Windows-Dev-AppConsult/Building-a-React-Native-module-for-Windows/ba-p/1067893" target="_blank" rel="noopener">my previous post about building native modules</A>, now you can build self-contained bundles for Windows, which don't require the Metro packager to be up &amp; running. It's enough to start the traditional process in Visual Studio to create an app package and choose the<SPAN>&nbsp;</SPAN><STRONG>Release</STRONG><SPAN>&nbsp;</SPAN>configuration mode to get an AppX / MSIX package that can be published on the Microsoft Store, side-loaded, deployed in an enterprise using SSCM or Intune, etc.</P> <P>&nbsp;</P> <P>However, when you start exploring this option, you face a few challenges, since the hosted agents provided by Azure DevOps miss some of the requirements to compile the various projects included in the React Native for Windows implementation. Let's see how to properly setup our pipeline.</P> <P>&nbsp;</P> <H3 id="create-the-pipeline">Create the pipeline</H3> <P>The first step, as always, is to have the source code of your React Native project committed on a source control repository. In my case I hosted it<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on GitHub</A>, but you can choose Azure Repos or any other Git provider. Then you need to move to the<SPAN>&nbsp;</SPAN><STRONG>Pipelines</STRONG><SPAN>&nbsp;</SPAN>section of your Azure DevOps project (create one if you don't have it) and click on<SPAN>&nbsp;</SPAN><STRONG>Create pipeline</STRONG>.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="CreatePipeline.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163365iF1673A266195B310/image-size/large?v=v2&amp;px=999" role="button" title="CreatePipeline.png" alt="CreatePipeline.png" /></span></P> <P>&nbsp;</P> <P>As first step, choose the repository where your code is hosted. As second step, you will be asked for a starting template for the YAML file, which defines the tasks that our pipeline will have to follow. Thanks to YAML you can enable what is called the<SPAN>&nbsp;</SPAN><STRONG>Infrastructure As Code</STRONG><SPAN>&nbsp;</SPAN>approach, which allows to provision hardware resources using a configuration file rather than interactive tools like a wizard. The advantage of this approach is that the configuration of your infrastructure (in this case, a build machine) becomes part of your project: it can be committed to the repository, it can be versioned, it can be easily replicated.</P> <P>The best template for our scenario is the<SPAN>&nbsp;</SPAN><STRONG>Universal Windows Platform</STRONG><SPAN>&nbsp;</SPAN>one, since React Native for Windows generates, under the hood, a UWP application.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="UWPTemplate.png" style="width: 667px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163366iB2726988AECCD4E3/image-size/large?v=v2&amp;px=999" role="button" title="UWPTemplate.png" alt="UWPTemplate.png" /></span></P> <P>&nbsp;</P> <P>This is the default YAML that gets created:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-20" class="language-yaml hljs"><SPAN class="hljs-comment"># Universal Windows Platform</SPAN> <SPAN class="hljs-comment"># Build a Universal Windows Platform project using Visual Studio.</SPAN> <SPAN class="hljs-comment"># Add steps that test and distribute an app, save build artifacts, and more:</SPAN> <SPAN class="hljs-comment"># https://aka.ms/yaml</SPAN> <SPAN class="hljs-attr">trigger:</SPAN> <SPAN class="hljs-bullet">-</SPAN> <SPAN class="hljs-string">master</SPAN> <SPAN class="hljs-attr">pool:</SPAN> <SPAN class="hljs-attr"> vmImage:</SPAN> <SPAN class="hljs-string">'windows-latest'</SPAN> <SPAN class="hljs-attr">variables:</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'**/*.sln'</SPAN> <SPAN class="hljs-attr"> buildPlatform:</SPAN> <SPAN class="hljs-string">'x86|x64|ARM'</SPAN> <SPAN class="hljs-attr"> buildConfiguration:</SPAN> <SPAN class="hljs-string">'Release'</SPAN> <SPAN class="hljs-attr"> appxPackageDir:</SPAN> <SPAN class="hljs-string">'$(build.artifactStagingDirectory)\AppxPackages\\'</SPAN> <SPAN class="hljs-attr">steps:</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">NuGetToolInstaller@1</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">NuGetCommand@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> restoreSolution:</SPAN> <SPAN class="hljs-string">'$(solution)'</SPAN> <SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">VSBuild@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> platform:</SPAN> <SPAN class="hljs-string">'x86'</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'$(solution)'</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">'$(buildConfiguration)'</SPAN> <SPAN class="hljs-attr"> msbuildArgs:</SPAN> <SPAN class="hljs-string">'/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=StoreUpload'</SPAN> </CODE></PRE> <P>Now we need to make a few changes. Let's see them, step by step.</P> <P>&nbsp;</P> <H4 id="choose-the-right-hosted-agent">Choose the right hosted agent</H4> <P>By default, the Universal Windows Platform template leverages the latest Windows hosted agent, which ships with Visual Studio 2019. However, there's a catch in our scenario. Many of the C++ projects involved in the React Native implementation are leveraging the C++ v141 toolset platform, which belongs to Visual Studio 2017. The Visual Studio installer offers you the option to install the v141 toolset in Visual Studio 2019 but, unfortunately, the<SPAN>&nbsp;</SPAN><STRONG>windows-latest</STRONG><SPAN>&nbsp;</SPAN>hosted agent doesn't include it. As such, we need to fallback to the hosted agent with Visual Studio 2017, by setting the<SPAN>&nbsp;</SPAN><CODE>vmImage</CODE><SPAN>&nbsp;</SPAN>property of the YAML file to<SPAN>&nbsp;</SPAN><CODE>vs2017-win2016</CODE>, as in the following example:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-58" class="language-yaml hljs"><SPAN class="hljs-attr">pool:</SPAN> <SPAN class="hljs-attr"> vmImage:</SPAN> <SPAN class="hljs-string">'vs2017-win2016'</SPAN></CODE></PRE> <H4>&nbsp;</H4> <H4 id="update-the-configuration-settings">Update the configuration settings</H4> <P>There are a few changes to make here compared to the default configuration. The most important one is the<SPAN>&nbsp;</SPAN><CODE>solution</CODE><SPAN>&nbsp;</SPAN>parameter, which by default is set to compile every solution included in the repository. However, React Native for Windows doesn't ship as a library or a NuGet package, but the whole source code is included as a Node module. As such, we need to specify that we want to compile only the solution which is included in the<SPAN>&nbsp;</SPAN><STRONG>windows</STRONG><SPAN>&nbsp;</SPAN>folder, which is the one that will generate the final AppX / MSIX package. As per the build platform, you're free to choose the ones you prefer: since React Native for Windows generates a Universal Windows Platform application, it supports x86, x64 and ARM. The build configuration, instead, must be<SPAN>&nbsp;</SPAN><STRONG>Release</STRONG>: as we have learned<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/Windows-Dev-AppConsult/Building-a-React-Native-module-for-Windows/ba-p/1067893" target="_blank" rel="noopener">in the previous post</A>, this is the compilation mode which generates a self-contained application.</P> <P>This is how the<SPAN>&nbsp;</SPAN><CODE>variables</CODE><SPAN>&nbsp;</SPAN>section of our YAML file should look like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-71" class="language-yaml hljs"><SPAN class="hljs-attr">variables:</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'windows/*.sln'</SPAN> <SPAN class="hljs-attr"> buildPlatform:</SPAN> <SPAN class="hljs-string">'x64'</SPAN> <SPAN class="hljs-attr"> buildConfiguration:</SPAN> <SPAN class="hljs-string">'Release'</SPAN> <SPAN class="hljs-attr"> appxPackageDir:</SPAN> <SPAN class="hljs-string">'$(build.artifactStagingDirectory)\AppxPackages\\'</SPAN></CODE></PRE> <P>&nbsp;</P> <H4 id="set-the-version-number">Set the version number</H4> <P>This isn't specific for React Native, but it applies to every application packaged as MSIX. If you want to have a reliable CI / CD pipeline, you must increase the version number at every build. Regardless if you choose to publish the app on the Microsoft Store or to deploy it in another way, an update must always have a higher version number than the previous release.</P> <P>The easiest way to achieve this task is to leverage the build number, since Azure DevOps automatically generates a new one at every build. However, the default one isn't a good fit for a packaged application. By default, in fact, Azure DevOps uses the following rule:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-84" class="language-yaml hljs"><SPAN class="hljs-string">$(Date:yyyyMMdd).$(Rev:r)</SPAN> </CODE></PRE> <P>which is translated to a build number like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-90" class="language-yaml hljs"><SPAN class="hljs-number">20191222.12</SPAN> </CODE></PRE> <P>If you have some experience with packaged applications, you'll immediately realize that this number won't work. The manifest of an AppX / MSIX package, in fact, requires the version number to follow the rule<SPAN>&nbsp;</SPAN><STRONG>x.y.z.0</STRONG>, so something like:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-96" class="language-yaml hljs"><SPAN class="hljs-number">2019.12</SPAN><SPAN class="hljs-number">.22</SPAN><SPAN class="hljs-number">.0</SPAN> </CODE></PRE> <P>As such, we need to change the default build number, by adding the following entry in the YAML file, before the<SPAN>&nbsp;</SPAN><CODE>steps</CODE><SPAN>&nbsp;</SPAN>section:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-102" class="language-yaml hljs"><SPAN class="hljs-attr">name:</SPAN> <SPAN class="hljs-string">$(date:yyyy).$(Month)$(rev:.r).0</SPAN> </CODE></PRE> <P>The next step is to inject this build number in the manifest of our application. The easiest way to do it is to install, in our Azure DevOps account, a 3rd party extension called<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Manifest Versioning Build Task</A>, developed by Richard Fennell. Once we have added it, we can simply add, as fist task in the<SPAN>&nbsp;</SPAN><CODE>steps</CODE><SPAN>&nbsp;</SPAN>section, the following entry:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-109" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">VersionAPPX@2</SPAN> <SPAN class="hljs-attr"> displayName:</SPAN> <SPAN class="hljs-string">'Version MSIX'</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> Path:</SPAN> <SPAN class="hljs-string">'$(Build.SourcesDirectory)'</SPAN> <SPAN class="hljs-attr"> VersionNumber:</SPAN> <SPAN class="hljs-string">'$(Build.BuildNumber)'</SPAN> <SPAN class="hljs-attr"> InjectVersion:</SPAN> <SPAN class="hljs-literal">true</SPAN> </CODE></PRE> <P>This task will simply take the build number (stored in the<SPAN>&nbsp;</SPAN><CODE>$(Build.BuildNumber)</CODE><SPAN>&nbsp;</SPAN>variable) and set it inside the manifest of our application.</P> <P>&nbsp;</P> <H4 id="use-the-right-version-of-nodejs">Use the right version of Node.js</H4> <P>The hosted agent comes with Node.js already installed. However, React Native requires a specific version to work properly. Using a newer version can lead to errors during the bundling process. As such, we need to add a task to install and set the correct Node.js version as default on the hosted agent, which is<SPAN>&nbsp;</SPAN><STRONG>12.9.1</STRONG>:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-124" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">UseNode@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> version:</SPAN> <SPAN class="hljs-string">'12.9.1'</SPAN></CODE></PRE> <P>&nbsp;</P> <H4 id="install-the-windows-10-1903-sdk">Install the Windows 10 1903 SDK</H4> <P>React Native for Windows projects are compiled using, as target SDK, the 1903 (build 18836) one. However, since this version is fairly new, it's included only in the<SPAN>&nbsp;</SPAN><STRONG>windows-latest</STRONG><SPAN>&nbsp;</SPAN>hosted agent and not in the<SPAN>&nbsp;</SPAN><STRONG>vs2017-win2016</STRONG><SPAN>&nbsp;</SPAN>one we're using. As such, if we want to avoid compilation errors, we need to install the SDK first on the hosted agent. The easiest way to do it is to leverage<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Chocolatey</A>, the popular package manager for Windows. Think of it like NuGet, but for Windows applications instead of development libraries. Among the many packages it offers, we can find also the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Windows 10 1903 SDK</A>. Chocolatey is already installed on every Windows hosted agent, so you just need to add the following task:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-133" class="language-yaml hljs"><SPAN class="hljs-attr">- script:</SPAN> <SPAN class="hljs-string">choco</SPAN> <SPAN class="hljs-string">install</SPAN> <SPAN class="hljs-string">windows-sdk-10-version-1903-all</SPAN></CODE></PRE> <P>&nbsp;</P> <H4 id="install-the-modules-used-by-our-react-native-application">Install the modules used by our React Native application</H4> <P>By default, the dependencies of a React Native application are not committed inside the repository, but they are restored the first time you build the application. This is a standard approach. This isn't code we have written, so it wouldn't make sense to increase the size of our repository for files that can be easily downloaded from Internet. We see the same, for example, with NuGet packages leveraged by .NET applications. As such, before building our project, we need to make sure that all the modules are downloaded and installed on the hosted agent. To achieve this goal we can use Yarn, which is installed as well on every hosted agent. As such, we just need to add the following task:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-141" class="language-yaml hljs"><SPAN class="hljs-attr">- script:</SPAN> <SPAN class="hljs-string">yarn</SPAN> <SPAN class="hljs-string">install</SPAN></CODE></PRE> <P>&nbsp;</P> <H4 id="install-the-react-native-cli">Install the React Native CLI</H4> <P>At some point, during the creation of the MSIX package, Visual Studio will launch the<SPAN>&nbsp;</SPAN><CODE>react-native bundle</CODE><SPAN>&nbsp;</SPAN>command, which takes care of bundling all the JavaScript files in a binary file, so that the application can run without the Metro packager. The<SPAN>&nbsp;</SPAN><CODE>react-native</CODE><SPAN>&nbsp;</SPAN>command is part of the React Native CLI, which isn't installed by default on the hosted agent. We need to install it first as a global tool, using NPM. However, there's a catch. When you install a package as global with NPM, it's installed only for the current user. The hosted agent, instead, performs all the tasks using a service which runs using a dedicated account, called<SPAN>&nbsp;</SPAN><STRONG>VssAdministrator</STRONG>. As such, we need to install the React Native CLI for this user, otherwise the Visual Studio build will fail because it won't find it.</P> <P>NPM supports setting the folder where to install global packages, by using the<SPAN>&nbsp;</SPAN><CODE>config</CODE><SPAN>&nbsp;</SPAN>parameter. Here are the tasks we need to launch:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-151" class="language-yaml hljs"><SPAN class="hljs-attr">- script:</SPAN> <SPAN class="hljs-string">npm</SPAN> <SPAN class="hljs-string">config</SPAN> <SPAN class="hljs-string">set</SPAN> <SPAN class="hljs-string">prefix</SPAN> <SPAN class="hljs-attr">C:\Users\VssAdministrator\AppData\Roaming\npm</SPAN> <SPAN class="hljs-attr">- script:</SPAN> <SPAN class="hljs-string">npm</SPAN> <SPAN class="hljs-string">install</SPAN> <SPAN class="hljs-bullet">-g</SPAN> <SPAN class="hljs-string">react-native-cli</SPAN> </CODE></PRE> <P>The first script sets, as installation folder for NPM packages, the<SPAN>&nbsp;</SPAN><STRONG>AppData</STRONG><SPAN>&nbsp;</SPAN>folder which belongs to the<SPAN>&nbsp;</SPAN><STRONG>VssAdministrator</STRONG><SPAN>&nbsp;</SPAN>user. The second script, instead, installs the React Native CLI in that folder.</P> <P>&nbsp;</P> <H4 id="restore-the-nuget-packages">Restore the NuGet packages</H4> <P>Before moving on we need to restore the NuGet packages. Many of the React Native for Windows projects, in fact, have dependencies on NuGet packages which are required by the whole solution to build.</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-161" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">NuGetCommand@2</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> command:</SPAN> <SPAN class="hljs-string">'restore'</SPAN> <SPAN class="hljs-attr"> restoreSolution:</SPAN> <SPAN class="hljs-string">'windows/*.sln'</SPAN> <SPAN class="hljs-attr"> feedsToUse:</SPAN> <SPAN class="hljs-string">'select'</SPAN> </CODE></PRE> <P>As you can notice, also in this case we specify that we want to restore only the NuGet packages referenced by the main solution, which is stored inside the<SPAN>&nbsp;</SPAN><STRONG>windows</STRONG><SPAN>&nbsp;</SPAN>folder.</P> <P>&nbsp;</P> <H4 id="build-the-visual-studio-solution">Build the Visual Studio solution</H4> <P>Now we can finally build the Visual Studio solution. Here we don't have to make too many changes to the default settings. This is the task I'm using:</P> <P>&nbsp;</P> <PRE><CODE id="pragma-line-184" class="language-yaml hljs"><SPAN class="hljs-attr">- task:</SPAN> <SPAN class="hljs-string">VSBuild@1</SPAN> <SPAN class="hljs-attr"> inputs:</SPAN> <SPAN class="hljs-attr"> solution:</SPAN> <SPAN class="hljs-string">'$(solution)'</SPAN> <SPAN class="hljs-attr"> msbuildArgs:</SPAN> <SPAN class="hljs-string">'/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Never /p:UapAppxPackageBuildMode=SideloadOnly /p:AppxPackageSigningEnabled=false'</SPAN> <SPAN class="hljs-attr"> platform:</SPAN> <SPAN class="hljs-string">'$(buildPlatform)'</SPAN> <SPAN class="hljs-attr"> configuration:</SPAN> <SPAN class="hljs-string">'$(buildConfiguration)'</SPAN> </CODE></PRE> <P>In my case, I opted to generate a MSIX package and not a bundle (<CODE>/p:AppxBundle=Never</CODE>) and to use it for sideloading (<CODE>/p:UapAppxPackageBuildMode=SideloadOnly</CODE>). If you want to generate a bundle (which is suggested if you want to support multiple CPU architectures or you have lot of graphical assets), just set the<SPAN>&nbsp;</SPAN><CODE>/p:AppBundle</CODE><SPAN>&nbsp;</SPAN>parameter to<SPAN>&nbsp;</SPAN><CODE>Always</CODE>. And if you want to publish your application on the Microsoft Store, just leave the<SPAN>&nbsp;</SPAN><CODE>/p:UapAppxPackageBuildMode</CODE><SPAN>&nbsp;</SPAN>parameter set to<SPAN>&nbsp;</SPAN><CODE>StoreUpload</CODE>. The only parameter which I strongly encourage to add is<SPAN>&nbsp;</SPAN><CODE>/p:AppxPackageSigningEnabled=false</CODE>, which will disable package signing during the build process. If you want to publish your application on the Microsoft Store, you don't need to digitally sign the package since the Store will do it for you. If, instead, you want to release it in other ways, you have to sign it but doing it during the build process performed by Visual Studio isn't considered a best practice. You would need, in fact, to upload the certificate on your repository, leaving you vulnerable to identity theft.</P> <P>&nbsp;</P> <H3 id="the-deployment">The deployment</H3> <P>Congratulations! Now, every time you'll commit new code to the repository, a build will be triggered and the hosted agent will create a new MSIX package with the latest version. That's the Continuous Integration part of our DevOps story. What about the Continuous Deployment? Once we have a MSIX package, we need to deploy it so that our users can get the most up-to-date version. We have different options: we can publish the package<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on the Microsoft Store</A>; or we can leverage a technology called<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">App Installer</A><SPAN>&nbsp;</SPAN>to deploy the package on a website and enable automatic updates from there.</P> <P>To achieve this goal you're going to build<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">a release pipeline</A>, which will allow you to deploy your application in multiple stages. Azure DevOps offer many built-in tasks which makes the deployment easier: you have tasks to connect to the Microsoft Store; tasks to digitally sign the package with a certificate; tasks to copy the package on an Azure Storage; etc.</P> <P>I won't explain all the details in this article. You can find all the information about the various options you have and how to implement them in the last chapter of my recently released e-book, called MSIX Succinctly,<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">which is available for free thanks to Syncfusion</A>. Alternatively, this topic is covered also by Exercise 6 of the<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Windows applications modernization workshop</A><SPAN>&nbsp;</SPAN>that my team has built.</P> <P>&nbsp;</P> <H3 id="whats-next">What's next?</H3> <P>In this post we have learned how to create a CI/CD pipeline for a React Native for Windows project. But there's a catch. Let's say you have a project which includes some custom native modules you have built, following the approach<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/Windows-Dev-AppConsult/Building-a-React-Native-module-for-Windows/ba-p/1067893" target="_blank" rel="noopener">described in my previous post</A>.</P> <P>If you setup a pipeline with the guidance described in this article, you'll start to hit a series of errors like the following ones during the Visual Studio build task:</P> <PRE><CODE id="pragma-line-217" class="language-plaintext hljs">[error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\AttributedViewManager.cs(447,21): Error CS8107: Feature 'default literal' is not available in C# 7.0. Please use language version 7.1 or greater. [error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\JSValue.cs(105,36): Error CS8107: Feature 'readonly references' is not available in C# 7.0. Please use language version 7.2 or greater. </CODE></PRE> <P>The<SPAN>&nbsp;</SPAN><STRONG>Microsoft.ReactNative.SharedManaged</STRONG><SPAN>&nbsp;</SPAN>project, which contains the implementation of the various classes and attributes which make easier to turn a class into a native module for React Native, leverages many features of the C# language that has been added after the 7.0 release. Many of these features require Visual Studio 2019, while our hosted agent is running Visual Studio 2017.</P> <P>Unfortunately, as we have learned in the beginning, due to the requirement of supporting the v141 C++ platform toolset we can't just switch our pipeline to use the<SPAN>&nbsp;</SPAN><STRONG>windows-latest</STRONG><SPAN>&nbsp;</SPAN>hosted agent. As such, we need to build a self-hosted agent with all our requirements.</P> <P>&nbsp;</P> <P>But that's food for the next blog post =)</img> In the meantime, you can review the final version of the pipeline we have built in a sample React Native project I put together<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on GitHub</A>. The repository contains a file called<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">azure-pipelines.yml</A>, which contains the full YAML file.</P> <P>&nbsp;</P> <P>Happy coding!</P> <P>&nbsp;</P> <P>&nbsp;</P> Thu, 02 Jan 2020 08:53:03 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/create-a-ci-cd-pipeline-for-a-react-native-for-windows/ba-p/1083253 Matteo Pagani 2020-01-02T08:53:03Z Microsoft Ignite 2019 Brazil – Windows Terminal demo https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/microsoft-ignite-2019-brazil-windows-terminal-demo/ba-p/1082104 <P>At Microsoft Ignite many attendees asked me more details about what is Windows Terminal and what are the main features.</P> <P>&nbsp;</P> <P>In this post I will share all the steps that I did on Ignite to achieve the following Window Terminal UI inspired on the demo that Kayla Cinnamon (@cinnamon_msft) did on her booth at Ignite:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="terminal.gif" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163206iD851930E4AE3AF18/image-size/large?v=v2&amp;px=999" role="button" title="terminal.gif" alt="terminal.gif" /></span></P> <P>&nbsp;</P> <H3>Windows Terminal Introduction</H3> <P>&nbsp;</P> <P>The Windows Terminal is a new, modern, fast, efficient, powerful, and productive terminal application for users of command-line tools and shells like Command Prompt, PowerShell, and WSL. Its main features include multiple tabs, Unicode and UTF-8 character support, a GPU accelerated text rendering engine, and custom themes, styles, and configurations.</P> <P>&nbsp;</P> <P>Windows Terminal is a Microsoft C++ open source project available for download and for community participation on <STRONG>Github:</STRONG></P> <P>&nbsp;</P> <P><A href="#" target="_blank" rel="noopener">https://github.com/microsoft/terminal</A></P> <P>&nbsp;</P> <P>Windows Terminal is also available for download on <STRONG>Microsoft Store</STRONG>: <A href="#" target="_blank" rel="noopener">https://www.microsoft.com/store/productId/9N0DX20HK701</A></P> <P>&nbsp;</P> <P>We can say that Windows Terminal is like a hub of shells, as you can customize it to have all your shells on it like CMD, PowerShell, PowerShell Core, Bash, Azure CLI and others.</P> <P>&nbsp;</P> <P>After installing Windows Terminal, you will see a UI like the following one that has 3 shells available being the PowerShell the default shell:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="01.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163208iA9F97814E00FA784/image-size/large?v=v2&amp;px=999" role="button" title="01.png" alt="01.png" /></span></P> <P>&nbsp;</P> <H3><U>Windows Terminal Settings</U></H3> <P>&nbsp;</P> <P>It is possible to customize Windows Terminal throw the <STRONG>profile.json</STRONG> file that can be accessed via the <STRONG>Settings wheel</STRONG> icon available in the Tab bar or by pressing “<STRONG>CTRL+,”</STRONG>.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="14.png" style="width: 294px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163210i9109FAFB7D03E1CC/image-size/large?v=v2&amp;px=999" role="button" title="14.png" alt="14.png" /></span></P> <P>&nbsp;</P> <P>If you edit it on <A title="VSCode" href="#" target="_self">Visual Studio Code (VSCode)</A>, you will have a better user experience, as VSCode has Intellisense:</P> <P>&nbsp;</P> <P>The <STRONG>profile.json</STRONG> file is available in the following address in case you what to set it to be automatically opened on VSCode:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">%localappdata%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\profiles.json </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>This is the <STRONG>profile.json</STRONG>&nbsp;file content with the three profiles related to the PowerShell, CMD and Azure Cloud Shell sessions:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="java">// To view the default settings, hold "alt" while clicking on the "Settings" button. // For documentation on these settings, see: https://aka.ms/terminal-documentation { "$schema": "https://aka.ms/terminal-profiles-schema", "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", "profiles": [ { // Make changes here to the powershell.exe profile "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", "name": "Windows PowerShell", "commandline": "powershell.exe", "hidden": false }, { // Make changes here to the cmd.exe profile "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", "name": "cmd", "commandline": "cmd.exe", "hidden": false }, { "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}", "hidden": false, "name": "Azure Cloud Shell", "source": "Windows.Terminal.Azure" } ], // Add custom color schemes to this array "schemes": [], // Add any keybinding overrides to this array. // To unbind a default keybinding, set the command to "unbound" "keybindings": [] } </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <H3>Step 1 - Adding the Acrylic effect</H3> <P>&nbsp;</P> <P>The first part of the demo is to add the Acrylic effect that allows us to change the Windows Terminal transparency.</P> <P>&nbsp;</P> <P>To add the acrylic effect, set the <STRONG>"useAcrylic": true</STRONG> property to the profile that you want to customize.</P> <P>&nbsp;</P> <P>Notice that you have Intellisent to assit you:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="02.png" style="width: 789px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163212i0E7FDF932ED4FCF7/image-size/large?v=v2&amp;px=999" role="button" title="02.png" alt="02.png" /></span></P> <P>&nbsp;</P> <P>You can also specify the background color and have the IntelliSense assist:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="03.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163213iC09CE824C9AA04BC/image-size/large?v=v2&amp;px=999" role="button" title="03.png" alt="03.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>It is possible to define the acrylic default opacity using the following properties:</P> <P><STRONG>"acrylicOpacity" : 0.8</STRONG></P> <P><STRONG>"backgroundImageOpacity" : 0.2</STRONG></P> <P>&nbsp;</P> <P>Alternatively, on the Windows Terminal UI, you can use the <STRONG>CTRL+Shift+Scroll Down</STRONG> to increase the transparency:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="04.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163214iBD5F321A10E88B2A/image-size/large?v=v2&amp;px=999" role="button" title="04.png" alt="04.png" /></span></P> <P>&nbsp;</P> <P>Use <STRONG>CTRL+Shift+Scroll Up</STRONG> to increase the opacity:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="05.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163215i14407F6FA6551FF8/image-size/large?v=v2&amp;px=999" role="button" title="05.png" alt="05.png" /></span></P> <P>&nbsp;</P> <H3>Step 2 – Add Ubuntu Shell</H3> <P>&nbsp;</P> <P>The first step is to enable the <STRONG>Windows Subsystem for Linux (WSL)</STRONG> optional feature available for Windows 10 Fall Creators Update and later:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="06.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163216iEF7B71C48C521188/image-size/large?v=v2&amp;px=999" role="button" title="06.png" alt="06.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Once enabled the WSL, it is necessary to open <STRONG>Microsoft Store</STRONG> and choose your favorite Linux distribution.</P> <P>&nbsp;</P> <P>In this demo, I am using <STRONG>Ubuntu</STRONG> distribution available at:</P> <P><A href="#" target="_blank" rel="noopener">https://www.microsoft.com/store/productId/9NBLGGH4MSV6</A></P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="08.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163217iBB8957A7C1D9C933/image-size/large?v=v2&amp;px=999" role="button" title="08.png" alt="08.png" /></span></P> <P>&nbsp;</P> <P>Once installed, open Ubuntu from the Start menu to complete the installation.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="09.png" style="width: 979px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163218i16E48EB0129EE588/image-size/large?v=v2&amp;px=999" role="button" title="09.png" alt="09.png" /></span></P> <P>&nbsp;</P> <P>You will be prompted to create a new user account (and its password):</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="10.png" style="width: 979px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163219i062089AA50CB4D5E/image-size/large?v=v2&amp;px=999" role="button" title="10.png" alt="10.png" /></span></P> <P>&nbsp;</P> <P>Open <STRONG>Windows Terminal</STRONG> and observe that the <STRONG>Ubuntu Shell</STRONG> is now available on Windows Terminal:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="12.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163220iCE9DC4A41369A5DF/image-size/large?v=v2&amp;px=999" role="button" title="12.png" alt="12.png" /></span></P> <P>&nbsp;</P> <P>It is possible to edit the profile.json to add Acrylic effect and a <STRONG>background image</STRONG> for example:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="15.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163221iFB7BA1F372DC06B8/image-size/large?v=v2&amp;px=999" role="button" title="15.png" alt="15.png" /></span></P> <P>&nbsp;</P> <P>Switch back to Windows Terminal and observe that the background image is displayed:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="16.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163222iEB36DE6DC3A282D0/image-size/large?v=v2&amp;px=999" role="button" title="16.png" alt="16.png" /></span></P> <P>&nbsp;</P> <P><STRONG>Install PowerShell Core</STRONG></P> <P>PowerShell Core uses .NET Core 2.x as its runtime and it supports the Windows, macOS, and Linux platforms.</P> <P>As PowerShell Core uses .NET Core, let’s install the .NET Core SDK on Window and Linux.</P> <P>&nbsp;</P> <H3><STRONG>Step 3 – Install PowerShell Core on Windows</STRONG></H3> <P>Download and install the <STRONG>.NET Core SDK</STRONG> installer on:</P> <P><A href="#" target="_blank" rel="noopener">https://dotnet.microsoft.com/download/dotnet-core/thank-you/sdk-3.1.100-windows-x64-installer</A></P> <P>&nbsp;</P> <P>After installed, run the following command on Windows Terminal to install the <STRONG>PowerShell Core</STRONG>:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">dotnet tool install --global PowerShell</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="17.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163223i46CFD1AD85F6935C/image-size/large?v=v2&amp;px=999" role="button" title="17.png" alt="17.png" /></span></P> <P>&nbsp;</P> <P>Since you just installed the PowerShell Core, you will need to reopen the Command Prompt window before running the tool you installed.</P> <P>&nbsp;</P> <P>After reopening the Windows Terminal, you can type&nbsp;<STRONG>pwsh</STRONG> to launch PowerShell Core or by the profile available on Windows Terminal menu:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="luisdem_0-1591904803735.png" style="width: 643px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/198209i0D421845C9BD30C8/image-dimensions/643x362?v=v2" width="643" height="362" role="button" title="luisdem_0-1591904803735.png" alt="luisdem_0-1591904803735.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <H3><STRONG>Step 4 – Install PowerShell Core on Ubuntu</STRONG></H3> <P>Open a terminal and run the following commands to register Microsoft key and feed:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">wget -q https://packages.microsoft.com/config/ubuntu/19.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="19.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163225i96B1679B46ACC3C6/image-size/large?v=v2&amp;px=999" role="button" title="19.png" alt="19.png" /></span></P> <P>&nbsp;</P> <P>Update the products available for installation, then install the .NET Core SDK. In your terminal, run the following commands:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">sudo apt-get update</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="20.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163226i7D1A2F9E5D9FB08A/image-size/large?v=v2&amp;px=999" role="button" title="20.png" alt="20.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="applescript">sudo apt-get update sudo apt install dotnet-sdk-3.1</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="22.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163228i385096CFABEF7566/image-size/large?v=v2&amp;px=999" role="button" title="22.png" alt="22.png" /></span></P> <P>&nbsp;</P> <P>Now that the .NET Core is installed, run the following command to install the <STRONG>PowerShell Core</STRONG>:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">dotnet tool install --global PowerShell</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="23.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163229i77A804005CD1D636/image-size/large?v=v2&amp;px=999" role="button" title="23.png" alt="23.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>In case you get an error, try to run the command again. Alternatively, you can use the following command to install PowerShell Core:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">sudo apt-get install -y powershell-preview </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Since you just installed the PowerShell Core, you will need to reopen the Command Prompt window before running the tool you installed.</P> <P>&nbsp;</P> <P>After reopening the Windows Terminal, type <STRONG>pwsh</STRONG> to launch <STRONG>PowerShell Core</STRONG>:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="25.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163230iDA59D60667D8342A/image-size/large?v=v2&amp;px=999" role="button" title="25.png" alt="25.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>You can also launch PowerShell Core from the shell list available in the tab menu:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="28.png" style="width: 704px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163233iEEE4E0353BE8A672/image-size/large?v=v2&amp;px=999" role="button" title="28.png" alt="28.png" /></span></P> <P>&nbsp;</P> <H3><STRONG>PowerShell Core Profile</STRONG></H3> <P>&nbsp;</P> <P>In the settings.json you can customize the PowerShell Core profile that was automatically created. In the statement before, I changed the name from PowerShell to "PowerShell Core" just to better differentiate that in the menu:</P> <P>&nbsp;</P> <DIV>&nbsp;</DIV> <P>&nbsp;</P> <LI-CODE lang="json">{ &nbsp;&nbsp;"guid":&nbsp;"{574e775e-4f2a-5b96-ac1e-a2962a402336}", &nbsp;&nbsp;"hidden":&nbsp;false, &nbsp;&nbsp;"name":&nbsp;"PowerShell&nbsp;Core", &nbsp;&nbsp;"source":&nbsp;"Windows.Terminal.PowershellCore", &nbsp;&nbsp;"startingDirectory"&nbsp;:&nbsp;"%USERPROFILE%", &nbsp;&nbsp;"background":&nbsp;"#113801", "backgroundImage": "https://media.giphy.com/media/9E7kUhnT9eDok/giphy.gif"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>To demonstrate that PowerShell Core is powerful, I set a GIF on the backgroundImage to achieve the following effect:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="27.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163232iF51BFB3E72365F8C/image-size/large?v=v2&amp;px=999" role="button" title="27.png" alt="27.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <H3><STRONG>Step 6 – Install ASCIIQUARIUM, CMATRIX and CACAFIRE</STRONG></H3> <P>This step was used in my Ignite demo to demonstrate that WSL is a real Linux edition that it can run Linux apps. The <STRONG>ASCIIQuarium</STRONG>, <STRONG>CMatrix</STRONG> and <STRONG>Cacafire</STRONG> apps will be used in the next session to demonstrate that it is possible to split the Windows Terminal in different sessions.</P> <P>&nbsp;</P> <P>To install <STRONG>ASCIIQuarium</STRONG>, open a Windows Terminal and run the following two commands in the Ubuntu shell:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">sudo add-apt-repository ppa:ytvwld/asciiquarium</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="29.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163234i9F5E6A28DE6E5204/image-size/large?v=v2&amp;px=999" role="button" title="29.png" alt="29.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">sudo apt-get update</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="30.png" style="width: 709px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163235i5D80162CB5E46B8F/image-size/large?v=v2&amp;px=999" role="button" title="30.png" alt="30.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">sudo apt-get install asciiquarium</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="31.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163236i964B53CCD5EE34BF/image-size/large?v=v2&amp;px=999" role="button" title="31.png" alt="31.png" /></span></P> <P>&nbsp;</P> <P>Type <STRONG>asciiquarium</STRONG> to run the app:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="32.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163237i82A96986291823AC/image-size/large?v=v2&amp;px=999" role="button" title="32.png" alt="32.png" /></span></P> <P>&nbsp;</P> <P>Run the following command to install <STRONG>CMATRIX:</STRONG></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">sudo apt-get install cmatrix</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="33.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163238i09E6C84123609001/image-size/large?v=v2&amp;px=999" role="button" title="33.png" alt="33.png" /></span></P> <P>&nbsp;</P> <P>Type <STRONG>cmatrix</STRONG> to launch the app:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="34.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163239iC9AD539193D3DCF6/image-size/large?v=v2&amp;px=999" role="button" title="34.png" alt="34.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Finally, type the following command to install cacafire:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">sudo apt-get install caca-utils</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="35.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163240i28912224D8094C6E/image-size/large?v=v2&amp;px=999" role="button" title="35.png" alt="35.png" /></span></P> <P>&nbsp;</P> <P>Type <STRONG>cacafire</STRONG> to launch the app:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="36.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163241i423588319C4CF473/image-size/large?v=v2&amp;px=999" role="button" title="36.png" alt="36.png" /></span></P> <P>&nbsp;</P> <H3><STRONG>Step 6 – How to split the session</STRONG></H3> <P>It is possible to split tab in different panes and use on each of the panes a different shell.</P> <P>&nbsp;</P> <P>Open the PowerShell Core and press <STRONG>Alt+Shift+plus</STRONG>&nbsp;to split the screen on vertical:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="37.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163242i3FE071CC6FEF51D6/image-size/large?v=v2&amp;px=999" role="button" title="37.png" alt="37.png" /></span></P> <P>&nbsp;</P> <P>On the right pane, type <STRONG>wsl</STRONG>&nbsp;and press <STRONG>enter</STRONG> to switch to Linux:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="38.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163243i53F8CA30EA2968C8/image-size/large?v=v2&amp;px=999" role="button" title="38.png" alt="38.png" /></span></P> <P>&nbsp;</P> <P>Still on the right pane, type <STRONG>cmatrix</STRONG> and press <STRONG>Alt+Shift+Right Arrow</STRONG> to expand the left pane to the right:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="39.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163244i38E7203D4F934BC8/image-size/large?v=v2&amp;px=999" role="button" title="39.png" alt="39.png" /></span></P> <P>&nbsp;</P> <P>On the right pane, type <STRONG>Alt+Shif+-</STRONG> to create a new panel horizontally. Type <STRONG>wsl </STRONG>and <STRONG>press enter&nbsp;</STRONG>to switch to Linux and type <STRONG>cacafire</STRONG>&nbsp;and <STRONG>press enter</STRONG> to launch the app:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="40.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163245i636D3F6DF56AC820/image-size/large?v=v2&amp;px=999" role="button" title="40.png" alt="40.png" /></span></P> <P>&nbsp;</P> <P>Now, on the right pane, type <STRONG>Alt+Shift+-</STRONG> to create a new panel horizontally, type <STRONG>wsl</STRONG> to switch to Linux and type <STRONG>asciiquarium</STRONG> to start that app:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="41.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163246i7E7A05BC6BAB34E6/image-size/large?v=v2&amp;px=999" role="button" title="41.png" alt="41.png" /></span></P> <P>&nbsp;</P> <H3><STRONG>CASCADIA CODE</STRONG></H3> <P>Observe that I am using Cascadia Code that is a Microsoft open source font available on <A href="#" target="_blank" rel="noopener">https://github.com/microsoft/cascadia-code</A></P> <P>&nbsp;</P> <P>You can define the Cascadia Code font to all profiles putting the information in the defaults session:</P> <P>&nbsp;</P> <LI-CODE lang="json"> "defaults": { // Put settings here that you want to apply to all profiles. "fontFace" : "Cascadia Code PL", "fontSize" : 10, "useAcrylic": true, "acrylicOpacity" : 0.8, "backgroundImageOpacity" : 0.2 }</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>Done! :smiling_face_with_smiling_eyes:</img></P> <P>In this post you learned how to recreate the demo that I did on Ignite:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="42.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163247i04390ABD0EE39B99/image-size/large?v=v2&amp;px=999" role="button" title="42.png" alt="42.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>I hope you liked.</P> <P>&nbsp;</P> <P>This demo was inspired on the demo that <STRONG>Kayla Cinnamon (@cinnamon_msft)</STRONG> did on her booth at Ignite:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="13.png" style="width: 435px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/163207i9FDB64DD426C68E1/image-dimensions/435x452?v=v2" width="435" height="452" role="button" title="13.png" alt="13.png" /></span></P> <P>&nbsp;</P> <P>&nbsp;</P> <P><STRONG>References</STRONG></P> <P>&nbsp;</P> <P>Windows Terminal 1.0<BR /><A href="#" target="_blank" rel="noopener">https://devblogs.microsoft.com/commandline/windows-terminal-1-0/</A></P> <P>&nbsp;</P> <P>What is Windows Terminal?</P> <P><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/terminal/</A></P> <P>&nbsp;</P> <P>Cascadia Code</P> <P><A href="#" target="_blank" rel="noopener">https://devblogs.microsoft.com/commandline/cascadia-code/</A></P> <P>&nbsp;</P> <P>How to make a pretty prompt in Windows Terminal with Powerline, Nerd Fonts, Cascadia Code, WSL, and oh-my-posh</P> <P><A href="#" target="_blank" rel="noopener">https://www.hanselman.com/blog/HowToMakeAPrettyPromptInWindowsTerminalWithPowerlineNerdFontsCascadiaCodeWSLAndOhmyposh.aspx</A></P> <P>&nbsp;</P> <P><SPAN>50+ Windows Terminal Themes: Light, Dark, and Colorful</SPAN></P> <P><A href="#" target="_blank" rel="noopener">https://allthings.how/windows-terminal-themes-light-dark-colorful/</A></P> <P>&nbsp;</P> <P>Custom key bindings in Windows Terminal</P> <P><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/terminal/customize-settings/key-bindings</A></P> <P>&nbsp;</P> <P>Windows Subsystem for Linux Installation Guide for Windows 10</P> <P><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/windows/wsl/install-win10</A></P> <P>&nbsp;</P> <P>.NET Core 3.1 Desktop Runtime (v3.1.0) - Windows x64 Installer</P> <P><A href="#" target="_blank" rel="noopener">https://dotnet.microsoft.com/download/dotnet-core/thank-you/runtime-desktop-3.1.0-windows-x64-installer</A></P> <P>&nbsp;</P> <P>Ubuntu 19.04 Package Manager - Install .NET Core</P> <P><SPAN><A href="#" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/dotnet/core/install/linux-package-manager-ubuntu-1904</A></SPAN></P> <P>&nbsp;</P> Thu, 11 Jun 2020 19:59:11 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/microsoft-ignite-2019-brazil-windows-terminal-demo/ba-p/1082104 luisdem 2020-06-11T19:59:11Z Building a React Native module for Windows https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-a-react-native-module-for-windows/ba-p/1067893 <P><STRONG>Update on 2nd January 2020:</STRONG></P> <P>The latest version of React Native for Windows has changed the namespace in which the&nbsp;<STRONG>IReactPackageProvider&nbsp;</STRONG>interface has been implemented. The previous one was:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="csharp">Microsoft.ReactNative.Bridge;</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>while the new one is:</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="csharp">Microsoft.ReactNative;</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>The snippets in the post have been updated to reflect this change.</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>React Native offers a wide range of modules to extend the core functionalities of the platform, developed either by the community or by the React team himself. These modules range from UI controls, helpers and services. Some of them are "generic" and they perform tasks which don't require access to any specific platform feature. For example,<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">Redux</A><SPAN>&nbsp;</SPAN>is a very popular state management library used in React applications but since, by default, everything is stored in memory, it basically runs everywhere. In fact, the same library can be used either with web applications (using React) or native applications (using React Native).</P> <P>&nbsp;</P> <P>However, there are many scenarios in the native development space where the only way to achieve a task is by interacting with the native APIs offered by the platform. Saving a file in the local storage; getting information about the device; accessing to the GPS are just a few examples. In this case, the module can't be generic, because each platform has its own way to access to native features. This is why React Native supports the concept of<SPAN>&nbsp;</SPAN><STRONG>native modules</STRONG>: a module that is leveraged in the main application through JavaScript but that, under the hood, invokes a native component specific for the platform where the application is running. Being native, each component isn't built in JavaScript, but using the native language used by the platform. For example, take a look at the popular module<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">AsyncStorage</A>, which can be used to read and write files in the local storage. As you can see, we have a folder called<SPAN>&nbsp;</SPAN><STRONG>ios</STRONG>, which contains a XCode project; the<SPAN>&nbsp;</SPAN><STRONG>android</STRONG><SPAN>&nbsp;</SPAN>folder, instead, contains some Java files. Both projects simply expose the same features, but implemented using the specific language and APIs from iOS and Android.</P> <P>&nbsp;</P> <P>If you have some experience with Xamarin and Xamarin Forms, this approach will be familiar. It's the same exact concept, in fact, behind Xamarin Plugins: the entry point is a C# wrapper which, under the hood, calls Swift, Java or UWP code based on the platform where the application is running.</P> <P>&nbsp;</P> <P>In this blog post we're going to learn how we can build such a native module with Windows support, so that we can access to native Windows APIs when our React Native application is running on Windows.</P> <P>&nbsp;</P> <H3 id="create-a-react-native-for-windows-project">Create a React Native for Windows project</H3> <P>As first step, let's create a React Native project with Windows support. I won't explain all the steps, since they are very well documented<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on the official GitHub repository</A>. Additionally, I already introduced React Native and the recently added Windows support<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/Windows-Dev-AppConsult/Getting-started-with-React-Native-for-Windows/ba-p/912093" target="_blank" rel="noopener">in another article</A>.</P> <P>Once you have completed all the steps, open the<SPAN>&nbsp;</SPAN><STRONG>windows</STRONG><SPAN>&nbsp;</SPAN>folder inside the project and double click on the Visual Studio solution. As we have learned<SPAN>&nbsp;</SPAN><A href="https://gorovian.000webhostapp.com/?exam=t5/Windows-Dev-AppConsult/Getting-started-with-React-Native-for-Windows/ba-p/912093" target="_blank" rel="noopener">in the previous article</A>, the solution contains a single application which is the host for our React Native content. In a regular scenario you won't need to touch the code here, since the whole development will happen in the JavaScript world. However, in our case we have to make an exception, since we need to interact with native Windows APIs to build our module.</P> <P>&nbsp;</P> <H3 id="create-the-module-with-a-windows-runtime-component">Create the module with a Windows Runtime Component</H3> <P>Let's start by adding a new project to the solution, which type is Windows Runtime Component. The component will give us access to all the Universal Windows Platform APIs. Additionally, being a WinRT component, it can be consumed regardless of the language used to build the main application. As a consequence, you're free to create a C# or C++ component, based on the language you're most familiar with. We'll be able to leverage it from the main React Native host (which is built in C++) regardless.</P> <P>In my case, C# is my favorite language, so I've chosen the<SPAN>&nbsp;</SPAN><STRONG>Windows Runtime Component (C#)</STRONG><SPAN>&nbsp;</SPAN>template. If you want to use C++, instead, you need to choose the<SPAN>&nbsp;</SPAN><STRONG>Windows Runtime Component (C++/WinRT)</STRONG><SPAN>&nbsp;</SPAN>template. If you don't see it, you can install it using<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">this extension</A>. Pay attention to not use the C++/CX template, because this approach to build C++ components for the Universal Windows Platform has been deprecated.</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="WindowsRuntimeComponent.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/161930i6DA560231901BF1A/image-size/large?v=v2&amp;px=999" role="button" title="WindowsRuntimeComponent.png" alt="WindowsRuntimeComponent.png" /></span></P> <P>&nbsp;</P> <P>Once the project has been created, you need to reference a couple of projects which are already included as part of the React Native for Windows solution. Right click on the project you have just created and choose<SPAN>&nbsp;</SPAN><STRONG>Add → Reference</STRONG>.</P> <UL> <LI>From the<SPAN>&nbsp;</SPAN><STRONG>Projects</STRONG><SPAN>&nbsp;</SPAN>section, choose<SPAN>&nbsp;</SPAN><STRONG>Microsoft.ReactNative</STRONG>. This is the core React Native implementation for Windows.</LI> <LI>From the<SPAN>&nbsp;</SPAN><STRONG>Shared Projects</STRONG><SPAN>&nbsp;</SPAN>section, choose<SPAN>&nbsp;</SPAN><STRONG>Microsoft.ReactNative.SharedManaged</STRONG>. This project will give us access to C# helpers that will make easy to build a native module. If you're using C++, the project to reference is called<SPAN>&nbsp;</SPAN><STRONG>Microsoft.ReactNative.Cxx</STRONG>.</LI> </UL> <P>These projects are very important because they define many attributes which will allow you to export the native code to JavaScript. This way, the developers who are going to use your module in their React Native projects don't need to know anything about C++, C# or Windows 10 APIs. They just need to use standard and plain JavaScript code.</P> <P>&nbsp;</P> <P>Before starting to write some code, expand the<SPAN>&nbsp;</SPAN><STRONG>References</STRONG><SPAN>&nbsp;</SPAN>section of the project, select the<SPAN>&nbsp;</SPAN><STRONG>Microsoft.ReactNative</STRONG><SPAN>&nbsp;</SPAN>library, right click on it and choose<SPAN>&nbsp;</SPAN><STRONG>Properties</STRONG>. Set<SPAN>&nbsp;</SPAN><STRONG>Copy Local</STRONG><SPAN>&nbsp;</SPAN>to<SPAN>&nbsp;</SPAN><STRONG>false</STRONG>, to make sure that there are no conflicts with the main application.</P> <P>Now we can start building our module. We're going to create a simple one to expose the information about the model of the device where the application is running. Let's start by renaming the default class included in the project with a more meaningful name, like<SPAN>&nbsp;</SPAN><STRONG>SampleComponent</STRONG>. This should trigger the renaming also of the class declared inside the code.</P> <P>Now we just need to define one or more methods, that will be exposed to the React Native application. In our case, we're going to define a single method that will return the current device name, using the<SPAN>&nbsp;</SPAN><CODE>EasClientDeviceInformation</CODE><SPAN>&nbsp;</SPAN>class included in the Universal Windows Platform:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-keyword">using</SPAN> Windows.Security.ExchangeActiveSyncProvisioning; <SPAN class="hljs-keyword">namespace</SPAN> <SPAN class="hljs-title">SampleReactModule</SPAN> { <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">SampleComponent</SPAN> { <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">string</SPAN> <SPAN class="hljs-title">GetDeviceModel</SPAN>()</SPAN> { EasClientDeviceInformation info = <SPAN class="hljs-keyword">new</SPAN> EasClientDeviceInformation(); <SPAN class="hljs-keyword">return</SPAN> info.SystemProductName; } } } </CODE></PRE> <P>The next step is to leverage the attributes provided by the<SPAN>&nbsp;</SPAN><STRONG>Microsoft.ReactNative</STRONG><SPAN>&nbsp;</SPAN>library to export the class and the method we have created to React Native. In our scenario we're going to use two of the many available ones:</P> <UL> <LI><CODE>[ReactModule]</CODE>, which we're using to decorate the whole class.</LI> <LI><CODE>[ReactMethod]</CODE>, which we're using to decorate every single method we want to expose.</LI> </UL> <P>This is the final look of our class:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-keyword">using</SPAN> Microsoft.ReactNative.Managed; <SPAN class="hljs-keyword">using</SPAN> Windows.Security.ExchangeActiveSyncProvisioning; <SPAN class="hljs-keyword">namespace</SPAN> <SPAN class="hljs-title">SampleReactModule</SPAN> { [<SPAN class="hljs-meta">ReactModule</SPAN>] <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">SampleComponent</SPAN> { [<SPAN class="hljs-meta">ReactMethod(<SPAN class="hljs-meta-string">"getDeviceModel"</SPAN>)</SPAN>] <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">string</SPAN> <SPAN class="hljs-title">GetDeviceModel</SPAN>()</SPAN> { EasClientDeviceInformation info = <SPAN class="hljs-keyword">new</SPAN> EasClientDeviceInformation(); <SPAN class="hljs-keyword">return</SPAN> info.SystemProductName; } } } </CODE></PRE> <P>Notice that we have passed the<SPAN>&nbsp;</SPAN><CODE>getDeviceModel</CODE><SPAN>&nbsp;</SPAN>value as parameter of the<SPAN>&nbsp;</SPAN><CODE>ReactMethod</CODE><SPAN>&nbsp;</SPAN>attribute. This is how we define the name of the method that will be available through JavaScript to the React Native project.</P> <P>There are many other attributes, which can be used to expose additional stuff to the React Native application, like properties or event handlers. They are all documented<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">here</A>.</P> <P>The next thing we need is a<SPAN>&nbsp;</SPAN><STRONG>ReactPackageProvider</STRONG>, which is a special object that will allow the React Native project to load all the modules exposed by our library. By default, in fact, a React Native application loads only the built-in modules. Right click on the project and choose<SPAN>&nbsp;</SPAN><STRONG>Add → Class</STRONG>. Name it<SPAN>&nbsp;</SPAN><STRONG>ReactPackageProvider</STRONG>. The class is very simple:</P> <P>&nbsp;</P> <PRE><CODE class="language-csharp hljs"><SPAN class="hljs-keyword">using</SPAN> Microsoft.ReactNative; <SPAN class="hljs-keyword">using</SPAN> Microsoft.ReactNative.Managed; <SPAN class="hljs-keyword">namespace</SPAN> <SPAN class="hljs-title">SampleReactModule</SPAN> { <SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">sealed</SPAN> <SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">ReactPackageProvider</SPAN>: <SPAN class="hljs-title">IReactPackageProvider</SPAN> { <SPAN class="hljs-function"><SPAN class="hljs-keyword">public</SPAN> <SPAN class="hljs-keyword">void</SPAN> <SPAN class="hljs-title">CreatePackage</SPAN>(<SPAN class="hljs-params">IReactPackageBuilder packageBuilder</SPAN>)</SPAN> { packageBuilder.AddAttributedModules(); } } } </CODE></PRE> <P>It just needs to implement the<SPAN>&nbsp;</SPAN><CODE>IReactPackageProvider</CODE><SPAN>&nbsp;</SPAN>interface, which will require you to implement the<SPAN>&nbsp;</SPAN><CODE>CreatePackage()</CODE><SPAN>&nbsp;</SPAN>method. Inside it we leverage the<SPAN>&nbsp;</SPAN><CODE>packageBuilder</CODE><SPAN>&nbsp;</SPAN>object and the<SPAN>&nbsp;</SPAN><CODE>AddAttributedModules()</CODE><SPAN>&nbsp;</SPAN>method to load all the modules we have decorated with the<SPAN>&nbsp;</SPAN><CODE>[ReactModule]</CODE><SPAN>&nbsp;</SPAN>attribute inside our project.</P> <P>&nbsp;</P> <H3 id="register-the-module-in-the-react-native-application">Register the module in the React Native application</H3> <P>As already mentioned, by default React Native for Windows registers only the modules which are included in the main project. If we would have created the<SPAN>&nbsp;</SPAN><STRONG>SampleComponent</STRONG><SPAN>&nbsp;</SPAN>class inside the main React Native project, we wouldn't have needed any additional step. However, in our case we are using a separate Windows Runtime Component, which allows us to leverage a different language (C#), so we need an extra step to register the library in the main application.</P> <P>&nbsp;</P> <P>First, right click on the React Native for Windows project and choose<SPAN>&nbsp;</SPAN><STRONG>Add → Reference</STRONG>. Select from the list the name of the Windows Runtime Component you have just created (in my sample, it's<SPAN>&nbsp;</SPAN><STRONG>SampleReactModule</STRONG>) and press Ok.</P> <P>&nbsp;</P> <P>Expand the<SPAN>&nbsp;</SPAN><STRONG>App.xaml</STRONG><SPAN>&nbsp;</SPAN>node in the main React Native project and double click on the<SPAN>&nbsp;</SPAN><STRONG>App.cpp</STRONG><SPAN>&nbsp;</SPAN>file. Inside the<SPAN>&nbsp;</SPAN><CODE>App</CODE><SPAN>&nbsp;</SPAN>constructor you will find the following entry:</P> <P>&nbsp;</P> <PRE><CODE class="language-cpp hljs">PackageProviders().Append(make&lt;ReactPackageProvider&gt;()); </CODE></PRE> <P>This is the code which loads all the modules that are included in the main project. Let's add below another registration for our new library:</P> <P>&nbsp;</P> <PRE><CODE class="language-cpp hljs">PackageProviders().Append(winrt::SampleReactModule::ReactPackageProvider()); </CODE></PRE> <P><CODE>SampleReactModule</CODE><SPAN>&nbsp;</SPAN>is the name of the Windows Runtime Component we have previously created, thus it's the namespace that contains our<SPAN>&nbsp;</SPAN><CODE>ReactPackageProvider</CODE><SPAN>&nbsp;</SPAN>implementation. To make this code compiling we need also to include the header file of our module at the top:</P> <P>&nbsp;</P> <PRE><CODE class="language-cpp hljs"><SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">include</SPAN> <SPAN class="hljs-meta-string">"winrt/SampleReactModule.h"</SPAN></SPAN> </CODE></PRE> <P>This is how the whole<SPAN>&nbsp;</SPAN><STRONG>App.cpp</STRONG><SPAN>&nbsp;</SPAN>file should look like:</P> <P>&nbsp;</P> <PRE><CODE class="language-cpp hljs"><SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">include</SPAN> <SPAN class="hljs-meta-string">"pch.h"</SPAN></SPAN> <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">include</SPAN> <SPAN class="hljs-meta-string">"App.h"</SPAN></SPAN> <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">include</SPAN> <SPAN class="hljs-meta-string">"ReactPackageProvider.h"</SPAN></SPAN> <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">include</SPAN> <SPAN class="hljs-meta-string">"winrt/SampleReactModule.h"</SPAN></SPAN> <SPAN class="hljs-keyword">using</SPAN> <SPAN class="hljs-keyword">namespace</SPAN> winrt::NativeModuleSample; <SPAN class="hljs-keyword">using</SPAN> <SPAN class="hljs-keyword">namespace</SPAN> winrt::NativeModuleSample::implementation; <SPAN class="hljs-comment">/// &lt;summary&gt;</SPAN> <SPAN class="hljs-comment">/// Initializes the singleton application object. This is the first line of</SPAN> <SPAN class="hljs-comment">/// authored code executed, and as such is the logical equivalent of main() or</SPAN> <SPAN class="hljs-comment">/// WinMain().</SPAN> <SPAN class="hljs-comment">/// &lt;/summary&gt;</SPAN> App::App() <SPAN class="hljs-keyword">noexcept</SPAN> { MainComponentName(<SPAN class="hljs-string">L"NativeModuleSample"</SPAN>); <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">if</SPAN> BUNDLE</SPAN> JavaScriptBundleFile(<SPAN class="hljs-string">L"index.windows"</SPAN>); InstanceSettings().UseWebDebugger(<SPAN class="hljs-literal">false</SPAN>); InstanceSettings().UseLiveReload(<SPAN class="hljs-literal">false</SPAN>); <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">else</SPAN></SPAN> JavaScriptMainModuleName(<SPAN class="hljs-string">L"index"</SPAN>); InstanceSettings().UseWebDebugger(<SPAN class="hljs-literal">true</SPAN>); InstanceSettings().UseLiveReload(<SPAN class="hljs-literal">true</SPAN>); <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">endif</SPAN></SPAN> <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">if</SPAN> _DEBUG</SPAN> InstanceSettings().EnableDeveloperMenu(<SPAN class="hljs-literal">true</SPAN>); <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">else</SPAN></SPAN> InstanceSettings().EnableDeveloperMenu(<SPAN class="hljs-literal">false</SPAN>); <SPAN class="hljs-meta">#<SPAN class="hljs-meta-keyword">endif</SPAN></SPAN> PackageProviders().Append(make&lt;ReactPackageProvider&gt;()); <SPAN class="hljs-comment">// Includes all modules in this project</SPAN> PackageProviders().Append(winrt::SampleReactModule::ReactPackageProvider()); InitializeComponent(); <SPAN class="hljs-comment">// This works around a cpp/winrt bug with composable/aggregable types tracked</SPAN> <SPAN class="hljs-comment">// by 22116519</SPAN> AddRef(); m_inner.as&lt;::IUnknown&gt;()-&gt;Release(); }</CODE></PRE> <P>&nbsp;</P> <H3 id="using-the-module-from-javascript">Using the module from JavaScript</H3> <P>That's it! Now we can move to the React Native project to start using the module we have just built from JavaScript. Open the folder which contains your React Native project with your favorite web editor. For me, it's Visual Studio Code. For simplicity, I've implemented the component which uses the native module directly in the<SPAN>&nbsp;</SPAN><STRONG>App.js</STRONG><SPAN>&nbsp;</SPAN>file of my React Native project, so that it will be the startup page.</P> <P>The first step is importing the<SPAN>&nbsp;</SPAN><CODE>NativeModule</CODE><SPAN>&nbsp;</SPAN>object exposed by React Native in our component:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">import</SPAN> { NativeModules } <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'react-native'</SPAN>; </CODE></PRE> <P>Our module will be exposed through this object, by leveraging the name of the class we have decorated with the<SPAN>&nbsp;</SPAN><CODE>[ReactModule]</CODE><SPAN>&nbsp;</SPAN>attribute. In our case, it's<SPAN>&nbsp;</SPAN><CODE>SampleComponent</CODE>, so we can use the following code to access to the<SPAN>&nbsp;</SPAN><CODE>GetDeviceModule()</CODE><SPAN>&nbsp;</SPAN>method:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs">NativeModules.SampleComponent.getDeviceModel(); </CODE></PRE> <P>Notice that we can reference the method with the lowercase name,<SPAN>&nbsp;</SPAN><CODE>getDeviceModel()</CODE>, thanks to the parameter we have passed to the<SPAN>&nbsp;</SPAN><CODE>[ReactMethod]</CODE><SPAN>&nbsp;</SPAN>attribute. However, by default the methods exposed by native modules are implemented in an asynchronous way using callbacks. As such, if we want to consume the<SPAN>&nbsp;</SPAN><CODE>getDeviceModel()</CODE><SPAN>&nbsp;</SPAN>method, we need to use the following approach:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs">getModel = <SPAN class="hljs-function"><SPAN class="hljs-params">()</SPAN> =&gt;</SPAN> { <SPAN class="hljs-keyword">var</SPAN> current = <SPAN class="hljs-keyword">this</SPAN>; NativeModules.SampleComponent.getDeviceModel(<SPAN class="hljs-function"><SPAN class="hljs-keyword">function</SPAN>(<SPAN class="hljs-params">result</SPAN>) </SPAN>{ current.setState({<SPAN class="hljs-attr">model</SPAN>: result}); }) } </CODE></PRE> <P>The method accepts a function, that is invoked once the asynchronous operation is completed. Inside this function we receive, as parameter, the result returned by our native method (in our case, the model of the device). In our sample, we store it inside the component's state, so that we can display it in the user interface using the JSX syntax:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs">&lt;Text&gt;Model: {<SPAN class="hljs-keyword">this</SPAN>.state.model}&lt;<SPAN class="hljs-regexp">/Text&gt; </SPAN></CODE></PRE> <P>Before calling the<SPAN>&nbsp;</SPAN><CODE>setState()</CODE><SPAN>&nbsp;</SPAN>method, however, we need to save a reference to the main context. Inside the callback, in fact, we have moved to a different context, so we don't have access to the helpers exposed by React Native.</P> <P>Below you can find the full definition of our component:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">import</SPAN> React, {Fragment} <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'react'</SPAN>; <SPAN class="hljs-keyword">import</SPAN> { StyleSheet, View, Text, StatusBar, Button } <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'react-native'</SPAN>; <SPAN class="hljs-keyword">import</SPAN> { NativeModules } <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'react-native'</SPAN>; <SPAN class="hljs-class"><SPAN class="hljs-keyword">class</SPAN> <SPAN class="hljs-title">App</SPAN> <SPAN class="hljs-keyword">extends</SPAN> <SPAN class="hljs-title">React</SPAN>.<SPAN class="hljs-title">Component</SPAN> </SPAN>{ <SPAN class="hljs-keyword">constructor</SPAN>(props) { <SPAN class="hljs-keyword">super</SPAN>(props); <SPAN class="hljs-keyword">this</SPAN>.state = { <SPAN class="hljs-attr">model</SPAN>: <SPAN class="hljs-string">''</SPAN>, } } getModel = <SPAN class="hljs-function"><SPAN class="hljs-params">()</SPAN> =&gt;</SPAN> { <SPAN class="hljs-keyword">var</SPAN> current = <SPAN class="hljs-keyword">this</SPAN>; NativeModules.SampleComponent.getDeviceModel(<SPAN class="hljs-function"><SPAN class="hljs-keyword">function</SPAN>(<SPAN class="hljs-params">result</SPAN>) </SPAN>{ current.setState({<SPAN class="hljs-attr">model</SPAN>: result}); }) } render() { <SPAN class="hljs-keyword">return</SPAN> ( &lt;View style={styles.sectionContainer}&gt; &lt;StatusBar barStyle="dark-content" /&gt; &lt;View&gt; &lt;Button title="Get model" onPress={this.getModel} /&gt; &lt;Text&gt;Model: {this.state.model}&lt;/Text&gt; &lt;/View&gt; &lt;/View&gt; ); } }; //styles definition export default App; </CODE></PRE> <P>If you're familiar with React Native, the code should be easy to understand:</P> <OL> <LI>We have defined a function called<SPAN>&nbsp;</SPAN><CODE>getModel()</CODE>, which takes care of interacting with our native module. The model of the device is stored inside the component's state, using the<SPAN>&nbsp;</SPAN><CODE>setState()</CODE><SPAN>&nbsp;</SPAN>function provided by React Native.</LI> <LI>The<SPAN>&nbsp;</SPAN><CODE>render()</CODE><SPAN>&nbsp;</SPAN>methods defines the UI component, using the JSX syntax. The UI is very simple: <UL> <LI>We have a<SPAN>&nbsp;</SPAN><CODE>&lt;Button&gt;</CODE>, which invokes the<SPAN>&nbsp;</SPAN><CODE>getModel()</CODE><SPAN>&nbsp;</SPAN>function by leveraging the<SPAN>&nbsp;</SPAN><CODE>onPress</CODE><SPAN>&nbsp;</SPAN>event.</LI> <LI>We have a<SPAN>&nbsp;</SPAN><CODE>&lt;Text&gt;</CODE>, which displays the value of the<SPAN>&nbsp;</SPAN><CODE>model</CODE><SPAN>&nbsp;</SPAN>property stored inside the state.</LI> </UL> </LI> </OL> <P>&nbsp;</P> <H3 id="testing-the-code">Testing the code</H3> <P>Now it's time to test the code! First, right click on the React Native project in Visual Studio and choose<SPAN>&nbsp;</SPAN><STRONG>Deploy</STRONG>. Before doing it, however, use the<SPAN>&nbsp;</SPAN><STRONG>Configuration Manager</STRONG><SPAN>&nbsp;</SPAN>dropdown to make sure you're targeting the right CPU architecture. By default, in fact, the solution will target ARM, so you will have to switch to x86 or x64. If it's the first time you build it, it will take a while since C++ compilation isn't exactly fast =)</img> Once it's done, open a command prompt on the folder which contains your React Native project and type<SPAN>&nbsp;</SPAN><STRONG>yarn start</STRONG>. This will launch the Metro packager, which will serve all the React Native components to your application. Once the dependency graph has been loaded, you can open the Start menu and launch the Windows application you have just deployed from Visual Studio. If you have done everything in the correct way, you will see the very simple UI we have built in our component. By pressing the button you will invoke the native module and you will see the model of your device being displayed:</P> <P>&nbsp;</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ReactNativeApp.png" style="width: 603px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/161931i3F5738A5D5FF1338/image-size/large?v=v2&amp;px=999" role="button" title="ReactNativeApp.png" alt="ReactNativeApp.png" /></span></P> <H3 id="improving-the-code">Improving the code</H3> <P>If you compare this development experience with the one you have when you add a 3rd party module to your project using yarn or npm, you realize that it's quite different. In such a case, in fact, you don't have to deal with the<SPAN>&nbsp;</SPAN><CODE>NativeModules</CODE><SPAN>&nbsp;</SPAN>object; or you don't need to use callbacks to call the various methods. Let's use again the example of the popular<SPAN>&nbsp;</SPAN><STRONG>AsyncStorage</STRONG><SPAN>&nbsp;</SPAN>module. If you want to store some data in the storage, you just import a reference to the<SPAN>&nbsp;</SPAN><CODE>AsyncStorage</CODE><SPAN>&nbsp;</SPAN>object and then you call an asynchronous method called<SPAN>&nbsp;</SPAN><CODE>setItem</CODE>:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">import</SPAN> AsyncStorage <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'@react-native-community/async-storage'</SPAN>; storeData = <SPAN class="hljs-keyword">async</SPAN> () =&gt; { <SPAN class="hljs-keyword">await</SPAN> AsyncStorage.setItem(<SPAN class="hljs-string">'@storage_Key'</SPAN>, <SPAN class="hljs-string">'stored value'</SPAN>) } </CODE></PRE> <P>Can we achieve the same goal with the native module we have just built? The answer is yes! We just need to build a JavaScript wrapper, that will make consuming our native module more straightforward.</P> <P>In Visual Studio Code add a new file in your React Native project and call it<SPAN>&nbsp;</SPAN><STRONG>SampleComponent.js</STRONG>. First, let's import the same<SPAN>&nbsp;</SPAN><CODE>NativeModules</CODE><SPAN>&nbsp;</SPAN>object we have previously imported in the main component:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">import</SPAN> { NativeModules } <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'react-native'</SPAN>; </CODE></PRE> <P>Now, using<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">JavaScript Promises</A>, we can build a wrapper to the<SPAN>&nbsp;</SPAN><CODE>getResult()</CODE><SPAN>&nbsp;</SPAN>method. Thanks to Promises, we can enable an easier approach to consume our asynchronous API, thanks to the<SPAN>&nbsp;</SPAN><CODE>async</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>keywords. If you have some C# background, this approach is similar to use the<SPAN>&nbsp;</SPAN><CODE>TaskCompletionSource</CODE><SPAN>&nbsp;</SPAN>class to build asynchronous operations that returns a<SPAN>&nbsp;</SPAN><CODE>Task</CODE><SPAN>&nbsp;</SPAN>object-</P> <P>This is how we can wrap the method:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">import</SPAN> { NativeModules } <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'react-native'</SPAN>; <SPAN class="hljs-keyword">export</SPAN> <SPAN class="hljs-keyword">const</SPAN> getDeviceModel = <SPAN class="hljs-function"><SPAN class="hljs-params">()</SPAN> =&gt;</SPAN> { <SPAN class="hljs-keyword">return</SPAN> <SPAN class="hljs-keyword">new</SPAN> <SPAN class="hljs-built_in">Promise</SPAN>(<SPAN class="hljs-function">(<SPAN class="hljs-params">resolve, reject</SPAN>) =&gt;</SPAN> { NativeModules.SampleComponent.getDeviceModel(<SPAN class="hljs-function"><SPAN class="hljs-keyword">function</SPAN>(<SPAN class="hljs-params">result, error</SPAN>) </SPAN>{ <SPAN class="hljs-keyword">if</SPAN> (error) { reject(error); } <SPAN class="hljs-keyword">else</SPAN> { resolve(result); } }) }) } </CODE></PRE> <P>The method returns a new<SPAN>&nbsp;</SPAN><CODE>Promise</CODE>, which requires us to fulfill two objects:</P> <UL> <LI><CODE>resolve</CODE>, which is invoked when the operation has completed successfully.</LI> <LI><CODE>reject</CODE>, which is invoked when the operation has failed.</LI> </UL> <P>Inside the Promise we invoke the<SPAN>&nbsp;</SPAN><CODE>getDeviceModel()</CODE><SPAN>&nbsp;</SPAN>method, in the same way we were doing before in the component. The only difference is that, in this case, once the callback is completed we pass the<SPAN>&nbsp;</SPAN><CODE>result</CODE><SPAN>&nbsp;</SPAN>to the<SPAN>&nbsp;</SPAN><CODE>resolve()</CODE><SPAN>&nbsp;</SPAN>method in case of success; otherwise, we pass the<SPAN>&nbsp;</SPAN><CODE>error</CODE><SPAN>&nbsp;</SPAN>to the<SPAN>&nbsp;</SPAN><CODE>reject()</CODE><SPAN>&nbsp;</SPAN>method.</P> <P>Now that we have built our wrapper, we can simplify the component we have previously built. First, remove the following line:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">import</SPAN> { NativeModules } <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'react-native'</SPAN>; </CODE></PRE> <P>Then replace it with the following one:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs"><SPAN class="hljs-keyword">import</SPAN> * <SPAN class="hljs-keyword">as</SPAN> SampleComponent <SPAN class="hljs-keyword">from</SPAN> <SPAN class="hljs-string">'./SampleComponent'</SPAN> </CODE></PRE> <P>This import will expose the functions we have defined in our wrapper through the<SPAN>&nbsp;</SPAN><CODE>SampleComponent</CODE><SPAN>&nbsp;</SPAN>object. Now you can change the<SPAN>&nbsp;</SPAN><CODE>getModel()</CODE><SPAN>&nbsp;</SPAN>function simply with this code:</P> <P>&nbsp;</P> <PRE><CODE class="language-javascript hljs">getModel = <SPAN class="hljs-keyword">async</SPAN> () =&gt; { <SPAN class="hljs-keyword">var</SPAN> model = <SPAN class="hljs-keyword">await</SPAN> SampleComponent.getDeviceModel(); <SPAN class="hljs-keyword">this</SPAN>.setState( { <SPAN class="hljs-attr">model</SPAN>: model}); } </CODE></PRE> <P>As you can see, thanks to Promises and the<SPAN>&nbsp;</SPAN><CODE>async</CODE><SPAN>&nbsp;</SPAN>and<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>keywords, the code is simpler to write and read. We just need to mark the function as<SPAN>&nbsp;</SPAN><CODE>async</CODE>, then call the<SPAN>&nbsp;</SPAN><CODE>SampleComponent.getDeviceModel()</CODE><SPAN>&nbsp;</SPAN>function with the<SPAN>&nbsp;</SPAN><CODE>await</CODE><SPAN>&nbsp;</SPAN>prefix. Since the asynchronous operation is managed for us, we can treat it like if it's synchronous and just store in a variable the result, which will contain the device model. Since we aren't using callbacks, we can also set the state directly by calling<SPAN>&nbsp;</SPAN><CODE>this.setState()</CODE>. If you are a C# developer, everything should be very familiar, since it's the same async and await approach supported by C#.</P> <P>That's it! Now launch the application again. In this case we didn't touch the native project, so if the Metro packager was still running, we should already be seeing the updated version. Of course there won't be any difference, since we didn't change the UI or the behavior, but the code is easier to read and maintain now.</P> <P>&nbsp;</P> <H3 id="exporting-the-module">Exporting the module</H3> <P>So far, we have added the native module implementation directly in the project. What if we want to create a true self-contained module, that we can install using yarn or npm like any other 3rd party module? Well, unfortunately the story isn't complete for Windows yet. By following the documentation<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">available on GitHub</A><SPAN>&nbsp;</SPAN>it's easy to create the skeleton of the module. We just need to create a new React Native module project, using the<SPAN>&nbsp;</SPAN><STRONG>create-react-native-module</STRONG><SPAN>&nbsp;</SPAN>CLI tool, add the React Native for Windows package and then:</P> <P>&nbsp;</P> <OL> <LI>Include a folder called<SPAN>&nbsp;</SPAN><STRONG>windows</STRONG><SPAN>&nbsp;</SPAN>with the Windows Runtime Component we have developed.</LI> <LI>Optionally include any JavaScript wrapper we might have created to make easier consuming the module.</LI> </OL> <P>If you already have a module with iOS and Android support, you'll just need instead to follow step 1 and include the windows folder with the Windows Runtime Component in your existing project.</P> <P>However, regardless of your scenario, the current React Native for Windows implementation lacks an important feature called<SPAN>&nbsp;</SPAN><STRONG>linking</STRONG>. You'll remember that, in the previous section, we had to manually edit the<SPAN>&nbsp;</SPAN><STRONG>App.cpp</STRONG><SPAN>&nbsp;</SPAN>file of the React Native project to load, through the<SPAN>&nbsp;</SPAN><CODE>ReactPackageProvider</CODE><SPAN>&nbsp;</SPAN>class, our Windows Runtime Component. On Android and iOS this operation isn't needed thanks to linking. You just need to run the<SPAN>&nbsp;</SPAN><CODE>react-native link</CODE><SPAN>&nbsp;</SPAN>command: it will take care of everything for you, without needing to manually edit the Android and iOS projects. React Native 0.60 has brought linking to the next level, by introducing<SPAN>&nbsp;</SPAN><STRONG>automatic linking</STRONG>. You don't even have to run the<SPAN>&nbsp;</SPAN><STRONG>link</STRONG><SPAN>&nbsp;</SPAN>command anymore; just add the package to your project and React Native will take of everything else for you. The React Native implementation for iOS and Android will automatically load all the 3rd party modules that have been added to the project. This feature isn't available on Windows yet and, as such, if you create an independent module and you publish it on NPM, you will still need to manually open the React Native for Windows solution in Visual Studio and register the module in the<SPAN>&nbsp;</SPAN><STRONG>App.cpp</STRONG><SPAN>&nbsp;</SPAN>file.</P> <P>&nbsp;</P> <P>Another important information to highlight is the language choice for your module. C++ has a performance advantage over C++, since it doesn't need to pull in the whole CLR in the React Native project. As such, especially if you're planning to share your module with the community, you should seriously consider to use C++. If, instead, you're aiming to use your module only in your project and you have verified that the performance trade off doesn't heavily affect your application, it's fine to keep using C#.</P> <H3 id="publishing-the-application">Publishing the application</H3> <P>Compared to the last time I wrote about React Native for Windows, there's a big news! Now you can create a deployable version of the application: an AppX / MSIX package which is self-contained and that doesn't need the Metro packager to be up &amp; running. This way, you can publish the application on the Microsoft Store or using one of the many supported deployment techniques (sideloading, SSCM, Intune, etc.). To achieve this goal just start the publishing process, like you would do with any Universal Windows Platform application. Right click on the React Native project in Visual Studio, choose<SPAN>&nbsp;</SPAN><STRONG>Publish → Create app packages</STRONG><SPAN>&nbsp;</SPAN>and follow the wizard. Just make sure, in the package configuration step, to choose<SPAN>&nbsp;</SPAN><STRONG>Release</STRONG><SPAN>&nbsp;</SPAN>as configuration mode. This way, all the React Native resources will be bundled together, removing the requirement of having the Metro packager up &amp; running.</P> <P>&nbsp;</P> <H3 id="wrapping-up">Wrapping up</H3> <P>In this post we have seen how you can build native modules for React Native for Windows. This will allow us to build applications using the React stack but, at the same time, leverage native features provided by Windows, like toast notifications, access to Bluetooth APIs, file system, etc. We have also learned how we can distribute an application built with React Native, either via the Microsoft Store or one of the many techniques supported by MSIX, like sideloading or Microsoft Intune.</P> <P>&nbsp;</P> <P>You can find the sample I've built for this post<SPAN>&nbsp;</SPAN><A href="#" target="_blank" rel="noopener">on GitHub</A>.</P> <P>&nbsp;</P> <P>Happy coding!</P> Fri, 22 May 2020 17:10:33 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/building-a-react-native-module-for-windows/ba-p/1067893 Matteo Pagani 2020-05-22T17:10:33Z Uno Platform が凄く面白そうなので紹介! https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/uno-platform-%E3%81%8C%E5%87%84%E3%81%8F%E9%9D%A2%E7%99%BD%E3%81%9D%E3%81%86%E3%81%AA%E3%81%AE%E3%81%A7%E7%B4%B9%E4%BB%8B/ba-p/1064964 <P>Uno Platform を少し触ってみた感じ面白そうだと感じた部分を紹介したいと思います。</P> <P>公式ページはこちらになります。</P> <P><A href="#" target="_blank">https://platform.uno/</A></P> <H2>簡単な説明</H2> <P>詳細は公式ドキュメントの <A href="#" target="_self">What is the Uno Platform?</A> にあるのでそちらを見ていただくとして個人的な解釈では UWP で開発した画面が、そのまま Android や iOS や Windows (当然ですが) や WebAssembly 上で動くといったものを目指してるものになります。</P> <P>&nbsp;</P> <P>Xamarin.Forms も同じように XAML + C# の組み合わせでクロスプラットフォームアプリケーションを開発可能ですが、Uno Platform と比べて一番の違いは ContentControl があることだと思ってます。</P> <P>WPF で登場した<A href="#" target="_self">コンテンツモデル</A>によって柔軟な見た目をコントロールで実現できるようになりました。Xamarin.Forms は XAML + C# で開発したものを、ネイティブコントロールにマッピングするということはしてくれていたのですが、このコンテンツモデルは持ち合わせていなかったので、例えば以下のような XAML は、Xamarin.Forms では実現できませんでした。</P> <P>&nbsp;</P> <LI-CODE lang="markup">&lt;Button HorizontalAlignment="Center" VerticalAlignment="Center"&gt; &lt;StackPanel Orientation="Horizontal"&gt; &lt;Ellipse Width="30" Height="30" Fill="Blue" /&gt; &lt;TextBlock Text="Rich button!!" /&gt; &lt;Ellipse Width="30" Height="30" Fill="Blue" /&gt; &lt;/StackPanel&gt; &lt;/Button&gt;</LI-CODE> <P>&nbsp;</P> <P>実行するとボタンの中に青い丸と、テキストが並んだ状態で表示されます。(左から Android、UWP、WebAssembly)</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_0.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/161619iC7EB7C550B93F6BA/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_0.png" alt="clipboard_image_0.png" /></span></P> <P>WPF、UWP と XAML での開発に慣れていた私は、無意識のうちにこの柔軟性をがあるのが XAML での開発だと思ってたので Xamarin.Forms による開発は若干不自由に感じていました。</P> <P>どっちがいいというのは無いと思っていて、Xamarin.Forms はレンダラーによってコントロールをネイティブプラットフォームのコントロールにマッピングすることでクロスプラットフォームの開発を実現していて、Uno は UWP で書いたものを各プラットフォームで動くようにしたというアプローチの違いです。</P> <P>&nbsp;</P> <P>WPF、UWP の開発のバックグラウンドがある人にとっては Uno Platform は、とてもとっつきやすいプラットフォームに仕上がってると思います。</P> <H2>プラットフォーム固有機能へのアクセス</H2> <P>プラットフォーム固有機能へのアクセス方法も提供されています。C# と XAML の両方で可能です。</P> <UL> <LI>C#:&nbsp;<A href="#" target="_blank">https://platform.uno/docs/articles/platform-specific-csharp.html</A></LI> <LI>XAML:&nbsp;<A href="#" target="_blank">https://platform.uno/docs/articles/platform-specific-xaml.html</A></LI> </UL> <P>C# のほうは #if で分岐したり partial class を使う例が紹介されていますが、コードが Shared プロジェクトにあることを考えると単に各プラットフォーム固有のプロジェクト(iOS, Droid, UWP, Wasm)に同じインターフェースを持った同名のクラスを作って、中身はプラットフォーム固有実装という形で対応できます。例えばユーザーに何かしらあったことを通知したいときに Android ではトースト、iOS ではアラート、UWP ではトースト通知、WebAssembly ではダイアログという形で実装した場合は、以下のようなクラスを各プロジェクトに用意します。</P> <P>&nbsp;</P> <P>Android プロジェクト</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using Android.App; using Android.Widget; namespace HelloUno { public class Toast { public static void Show(string text) { Android.Widget.Toast.MakeText(Application.Context, text, ToastLength.Long).Show(); } } }</LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>iOS プロジェクト</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using UIKit; namespace HelloUno { public class Toast { public static void Show(string text) { var alert = UIAlertController.Create("Toast", text, UIAlertControllerStyle.Alert); alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, _ =&gt; { })); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null); } } }</LI-CODE> <P>&nbsp;</P> <P>UWP プロジェクト</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using System.Linq; using Windows.UI.Notifications; namespace HelloUno { public class Toast { public static void Show(string text) { var n = ToastNotificationManager.CreateToastNotifier(); var content = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01); var textNode = content.GetElementsByTagName("text").First(); textNode.InnerText = text; n.Show(new ToastNotification(content)); } } }</LI-CODE> <P>&nbsp;</P> <P>WebAssembly 用プロジェクト</P> <P>&nbsp;</P> <LI-CODE lang="csharp">using System; using Windows.UI.Popups; namespace HelloUno { public class Toast { public static async void Show(string text) { await new MessageDialog(text).ShowAsync(); } } }</LI-CODE> <P>&nbsp;</P> <P>iOS は手元にないので確認できていませんが、Android と UWP と WebAssembly では以下のようになります。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/161622iF073F71ABDE3B08C/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_1.png" alt="clipboard_image_1.png" /></span></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_2.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/161623i1FB7750E57FD4307/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_2.png" alt="clipboard_image_2.png" /></span></P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_3.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/161624i7D9B8F1419726C2F/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_3.png" alt="clipboard_image_3.png" /></span></P> <P>実際には一部だけプラットフォーム固有処理に切り出したいというケースとかもあるので partial method でプラットフォーム固有部分を切り出すか、今回のような完全にプラットフォーム固有処理は別々に作る場合もプラットフォーム間で実装に差異が生まれないようい Shared に interface を定義して、各プラットフォームのプロジェクトで実装という形にすると思います。</P> <P>&nbsp;</P> <P>XAML でのプラットフォーム固有機能を使う場合も上記ドキュメントにあるように XML 名前空間を使ってすっきりと書けます。さらに、xmlns:native_android="using:Android.Widget" といったようなネイティブのコントロールの名前空間を定義して、xmlns:android="<A href="#" target="_blank">http://uno.ui/android</A>" のようなプラットフォーム固有機能を書く機能と組み合わせると、ネイティブのコントロールを明示的に置くこともできます。</P> <P>&nbsp;</P> <P>以下のような XAML を書いて</P> <P>&nbsp;</P> <LI-CODE lang="markup">&lt;Page x:Class="HelloUno.MainPage" xmlns="<A href="#" target="_blank">http://schemas.microsoft.com/winfx/2006/xaml/presentation</A>" xmlns:x="<A href="#" target="_blank">http://schemas.microsoft.com/winfx/2006/xaml</A>" xmlns:local="using:HelloUno" xmlns:d="<A href="#" target="_blank">http://schemas.microsoft.com/expression/blend/2008</A>" xmlns:mc="<A href="#" target="_blank">http://schemas.openxmlformats.org/markup-compatibility/2006</A>" xmlns:android="<A href="#" target="_blank">http://uno.ui/android</A>" xmlns:native_android="using:Android.Widget" mc:Ignorable="d android"&gt; &lt;Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"&gt; &lt;Button HorizontalAlignment="Center" VerticalAlignment="Center" Click="Button_Click"&gt; &lt;StackPanel Orientation="Horizontal"&gt; &lt;Ellipse Width="30" Height="30" Fill="Blue" /&gt; &lt;TextBlock Text="Rich button!!" VerticalAlignment="Center" Margin="10" /&gt; &lt;android:Border&gt; &lt;native_android:TextView Text="Android Native!!"/&gt; &lt;/android:Border&gt; &lt;Ellipse Width="30" Height="30" Fill="Blue" /&gt; &lt;/StackPanel&gt; &lt;/Button&gt; &lt;/Grid&gt; &lt;/Page&gt;</LI-CODE> <P>&nbsp;</P> <P>実行すると Android 側にだけ追加でテキストが表示されています。</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_4.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/161627i8F81F0FC292690B1/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_4.png" alt="clipboard_image_4.png" /></span></P> <H2>Uno Platform を使ってるアプリ</H2> <P>以下の Issue で Uno Platform を使ってるアプリが紹介されてます。興味があったら実際にインストールしてみるといいかもしれません。</P> <P><A href="#" target="_blank">https://github.com/unoplatform/uno/issues/18</A></P> <H2>まとめ</H2> <P>Uno Platform は、WPF や UWP をやっていた人が、これ欲しかったと思うようなクロスプラットフォームアプリケーション開発ツールになってます。</P> <P>愚直に UWP の API を各ネイティブにマッピングしてるので、ソースコードを見てみると面白いです。</P> <P>&nbsp;</P> <P>個人的なお勧めは Uno.UI フォルダー以下です。</P> <P><A href="#" target="_blank">https://github.com/unoplatform/uno/tree/master/src/Uno.UI</A></P> <P>&nbsp;</P> <P>例えば ContentControl の UpdateContentTemplateRoot メソッドとか頑張ってコンテンツモデルを再現してる感じがうかがえます。</P> <P><A href="#" target="_blank">https://github.com/unoplatform/uno/blob/master/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.cs#L308</A></P> <P>&nbsp;</P> <P>ということで Uno Platform は、これからも継続的にウォッチしていこうと思います。</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> Fri, 13 Dec 2019 11:59:57 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/uno-platform-%E3%81%8C%E5%87%84%E3%81%8F%E9%9D%A2%E7%99%BD%E3%81%9D%E3%81%86%E3%81%AA%E3%81%AE%E3%81%A7%E7%B4%B9%E4%BB%8B/ba-p/1064964 KazukiOta 2019-12-13T11:59:57Z Let's look WinUI 3.0 alpha https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/let-s-look-winui-3-0-alpha/ba-p/982830 <P>At Ignite 2019, WinUI 3.0 Alpha was released. The details are as following links:</P> <UL> <LI><A href="#" target="_self">WinUI 3.0 Alpha (November 2019)</A></LI> <LI><A href="#" target="_self">Windows UI Library Roadmap</A></LI> </UL> <P>From <A href="#" target="_self">the link</A>, you can get a vsix file to project templates for WinUI 3.0. After installing it, you can find many templates for WinUI.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_0.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/154837i70B23E3904578F58/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_0.png" alt="clipboard_image_0.png" /></span></P> <H2>Let's look the Blank App(WinUI UWP) C#</H2> <P>There is a Microsoft.WinUI version 3.0.0-alpha.19101.0 package reference. Under Microsoft.WinUI.Controls namespace, there are already a lot of controls, we can use them.(But <A href="#" target="_self">there are a few limitation in alpha version</A>.)</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_2.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/154853iA7490D2DD28A79A4/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_2.png" alt="clipboard_image_2.png" /></span></P> <P>&nbsp;</P> <P>It works almost all fine.(Alpha version has a few limitation, for ex: transparency, etc...)</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_1.png" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/154851i4A86AD235A550A76/image-size/large?v=v2&amp;px=999" role="button" title="clipboard_image_1.png" alt="clipboard_image_1.png" /></span></P> <H2>Features still not implemented in this version</H2> <P>One of great features of WinUI 3.0 I think is to support XAML Islands. XAML Islands is working currently on Windows 10 1903.</P> <P>However, WinUI 3.0 is going to support Windows 10 Creators Update or above. After end of Windows 7 support, we can use all UWP controls on our Win32 apps(exclude running on Windows 8.1).</P> <P>I'm really looking forward to be released it.</P> <P>&nbsp;</P> <H2>Conclusion</H2> <P>I just looked a project template for Blank App(WinUI UWP) in this article.</P> <P>It was already worked fine.</P> <P>I think it is great milestone for Windows Developers, if you feel it is interesting, then please check update info of Windows UI Library from the repository.</P> <P><A href="#" target="_blank">https://github.com/microsoft/microsoft-ui-xaml</A></P> <P>&nbsp;</P> <P>Happy developing Windows app!</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> Tue, 05 Nov 2019 10:17:17 GMT https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/let-s-look-winui-3-0-alpha/ba-p/982830 KazukiOta 2019-11-05T10:17:17Z Let's use XAML Island on WPF app without changing C# code behind https://gorovian.000webhostapp.com/?exam=t5/windows-dev-appconsult/let-s-use-xaml-island-on-wpf-app-without-changing-c-code-behind/ba-p/950844 <P>Final answer is case by case. However, if you had been creating apps using MVVM pattern, then there is possible that it's easy to migrate to UWP controls using XAML Islands.</P> <P>&nbsp;</P> <P>In this article, I migrate to UWP controls using XAML Islands from WPF controls.<BR /><BR /></P> <H2>The sample app</H2> <P>I created a simple application like below:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="itemmanager.gif" style="width: 999px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/148693i8F4392E403F0AEB8/image-size/large?v=v2&amp;px=999" role="button" title="itemmanager.gif" alt="itemmanager.gif" /></span></P> <P><BR />The app is a really simple MVVM app. MainPage.xaml is as below:</P> <P>&nbsp;</P> <P>&nbsp;</P> <LI-CODE lang="markup">&lt;Window x:Class="WpfApp.Views.MainWindow" xmlns="<A href="#" target="_blank">http://schemas.microsoft.com/winfx/2006/xaml/presentation</A>" xmlns:x="<A href="#" target="_blank">http://schemas.microsoft.com/winfx/2006/xaml</A>" xmlns:d="<A href="#" target="_blank">http://schemas.microsoft.com/expression/blend/2008</A>" xmlns:mc="<A href="#" target="_blank">http://schemas.openxmlformats.org/markup-compatibility/2006</A>" xmlns:local="clr-namespace:WpfApp.Views" xmlns:viewModels="clr-namespace:WpfApp.ViewModels" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"&gt; &lt;Window.DataContext&gt; &lt;viewModels:MainWindowViewModel /&gt; &lt;/Window.DataContext&gt; &lt;Grid&gt; &lt;Grid.ColumnDefinitions&gt; &lt;ColumnDefinition /&gt; &lt;ColumnDefinition /&gt; &lt;/Grid.ColumnDefinitions&gt; &lt;local:ItemsTreeView SelectionChanged="ItemsTreeView_SelectionChanged" /&gt; &lt;local:ContentItemView Grid.Column="1" /&gt; &lt;/Grid&gt; &lt;/Window&gt; </LI-CODE> <P>&nbsp;</P> <P>&nbsp;</P> <P>&nbsp;</P> <P>The left side is a UserControl that name is ItemsTreeView. The UserControl is to display TreeView and raise a SelectionChanged event when selection changed on the TreeView.</P> <P>&nbsp;</P> <P>The right side is a UserControl that name is ContentItemView. The UserControl is to provide displaying and editing features to selected item on the TreeView.</P> <DIV id="tinyMceEditorclipboard_image_0" class="mceNonEditable lia-copypaste-placeholder">&nbsp;</DIV> <P>A following picture is a project structure of the app.</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_1.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/148778i348633208258ADAD/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_1.png" alt="clipboard_image_1.png" /></span></P> <P>WpfApp.Models project is a Class Library project of .NET Standard 2.0. WpfApp project is a WPF App(.NET Core) project.<BR /><BR /></P> <H2>Let's set up for XAML Islands</H2> <P>The details is written on <A href="#" target="_self">this document</A>. I have set up my solution like following:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_2.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/148805i14F8DB58150E0995/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_2.png" alt="clipboard_image_2.png" /></span></P> <P>&nbsp;</P> <P>UWPApp project and UWPApp.Controls project references WpfApp.Models project.</P> <P>And I've created two UserControls to UWPApp.Controls project:</P> <P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="clipboard_image_3.png" style="width: 400px;"><img src="https://techcommunity.microsoft.com/t5/image/serverpage/image-id/148814iC11AA209307F08C0/image-size/medium?v=v2&amp;px=400" role="button" title="clipboard_image_3.png" alt="clipboard_image_3.png" /></span></P> <P>&nbsp;</P> <P>Those are implemented same as WPF's ones. For example, TreeView