Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
C# 8 and .NET Core 3 Projects Using Azure

You're reading from   C# 8 and .NET Core 3 Projects Using Azure Build professional desktop, mobile, and web applications that meet modern software requirements

Arrow left icon
Product type Paperback
Published in Dec 2019
Publisher
ISBN-13 9781789612080
Length 528 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Authors (3):
Arrow left icon
 Michaels Michaels
Author Profile Icon Michaels
Michaels
 Strauss Strauss
Author Profile Icon Strauss
Strauss
 Rademeyer Rademeyer
Author Profile Icon Rademeyer
Rademeyer
Arrow right icon
View More author details
Toc

Table of Contents (13) Chapters Close

Preface 1. Ebook Manager and Catalogue App - .NET Core for Desktop FREE CHAPTER 2. Task Bug Logging ASP.NET Core MVC App Using Cosmos DB 3. ASP.NET Azure SignalR Chat Application 4. Web Research Tool with Entity Framework Core 5. Building a Twitter Automated Campaign Manager Using Azure Logic Apps and Functions 6. Stock Checker Using Identity Server and OAuth 2 7. Building a Photo Storage App Using a Windows Service and Azure Storage 8. A Load-Balanced Order Processing Microservice Using Docker and Azure Kubernetes Service 9. Emotion Detector Mobile App - Using Xamarin Forms and Azure Cognitive Services 10. Eliza for the 21st Century - UWP and the MS Bot Framework 11. WebAssembly 12. Other Books You May Enjoy

Exploring XAML Islands

For this section, you will need to be running Windows 10 1903 or later. By the time this book is published, it is expected that the 1903 release will be delivered automatically to all Windows 10 machines; however, if you are running an earlier version, then you can force an update by visiting the following link: https://www.microsoft.com/en-us/software-download/windows10.

In 2019, when this chapter was written, we noted that the TreeView in the import books section looks a little dated. In fact, you'd think it was a TreeView from 2005 when WinForms was all the rage! Also, we'd like to bind our data to the TreeView, rather than build it up separately. While there are some data binding capabilities in WinForms, we are stuck with the general appearance of TreeView.

Unless, that is, we use one of the nice new UWP controls in WinForms. That's exactly what XAML Islands gives us! We can take an existing UWP control, or even create our own, and use it directly from an existing WinForms application.

Let's try and use the TreeView from the UWP Community Toolkit inside our WinForms application.

UWP TreeView

There are a number of setup requirements for this, which I'll detail later.

By the time this is published, the process for setting this up may have been simplified considerably; please refer to the linked articles for the most recent advice.

The first step is to ensure (as detailed in the Technical requirements section) that you're running Windows 10, version 1903 or later. Please follow the information in that section if you are not. The second step is to install the Windows 10 SDK; for this, you can use the following link: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk.

We will be performing the following article for the next step: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance#set-up-your-project.

Add the following NuGet package to your WinForms project:

Microsoft.Windows.SDK.Contracts

Install the XamlHost NuGet package into the WinForms app:

Install-Package Microsoft.Toolkit.Forms.UI.XamlHost

Now we can replace our existing TreeView with the UWP one.

You'll notice that I've fully qualified all the XAML controls. Since we're dealing with two disparate frameworks, this kind of change makes it very easy to get confused and mix up which control you're dealing with.
In the following code samples, I've included class-level variables with the code samples for clarity. I, personally, would suggest that these actually go at the top of your class file. Of course, it makes no functional difference.

The first thing that we need to consider is XamlHost.

WIndowsXamlHost

Let's create our TreeView; we'll do this in the code-behind for ImportBooks.cs. We're going to add some code to the constructor, which will now look as follows:

private readonly Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHost _windowsXamlHostTreeView;        

public ImportBooks() { InitializeComponent(); _jsonPath = Path.Combine(Application.StartupPath, "bookData.txt"); spaces = spaces.ReadFromDataStore(_jsonPath); var windowsXamlHostTreeView = new WindowsXamlHost(); windowsXamlHostTreeView.InitialTypeName = "Windows.UI.Xaml.Controls.TreeView"; windowsXamlHostTreeView.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; windowsXamlHostTreeView.Location = new System.Drawing.Point(12, 60); windowsXamlHostTreeView.Name = "tvFoundBooks"; windowsXamlHostTreeView.Size = new System.Drawing.Size(513, 350); windowsXamlHostTreeView.TabIndex = 8; windowsXamlHostTreeView.Dock = System.Windows.Forms.DockStyle.None; windowsXamlHostTreeView.ChildChanged += windowsXamlHostTreeView_ChildChanged; this.Controls.Add(windowsXamlHostTreeView); }

Let's quickly review what we've done here (it's actually not that much). Firstly, we've created a new WIndowsXamlHost object. This is the basis for XAML Islands; it acts as a wrapper around your UWP control, so it will work in a WinForms context.


Although this chapter discusses WinForms, the same is true for WPF and, while the exact syntax may differ slightly, the basic principle is the same.

The things to notice on this code sample are as follows:

  • We're setting the name to tvFoundBooks, which is the same name as our WinForms app had.
  • We're listening to the ChildChanged event: this is so that we can set some specifics on the control itself (we'll come back to this shortly).
  • The InitialTypeName is how XAML Islands knows which UWP control to invoke.
  • We're adding the host control to the current form (we also set the location).

ItemTemplate

Now that we've set up the host control, we can have a look at the ChildChanged event that we mentioned; this is where we set up the UWP control (rather than the host control):

private Windows.UI.Xaml.Controls.TreeView? _tvFoundBooks = null;

private
void windowsXamlHostTreeView_ChildChanged(object? sender, EventArgs e) { if (sender == null) return; var host = (WindowsXamlHost)sender; _tvFoundBooks = (Windows.UI.Xaml.Controls.TreeView)host.Child; _tvFoundBooks.ItemInvoked += _tvFoundBooks_ItemInvoked; _tvFoundBooks.ItemsSource = DataSource; const string Xaml = "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><TreeViewItem ItemsSource=\"{Binding Children}\" Content=\"{Binding Name}\"/></DataTemplate>"; var xaml = XamlReader.Load(Xaml); _tvFoundBooks.ItemTemplate = xaml as Windows.UI.Xaml.DataTemplate; }

Don't worry so much about why _tvFoundBooks is a class-level variable, we'll come back to that shortly. In the preceding code sample, we have a gated check to ensure that sender is not null, and then we're forcing it to a WindowsXamlHost type. Once we have this type, we can get whatever is inside the host by calling the .Child property.

As before, we're listening to the ItemInvoked event (again, we'll come back to this shortly). The first really new thing here is that we're setting the ItemsSource, and the ItemTemplate. We'll come back to ItemsSource, but the template is worth exploring. Unlike WinForms, UWP uses XAML to define how its controls look. This means that you have control over exactly what goes into the TreeView; for example, each node could have an image, or text, or both. However, if you don't specify ItemTemplate, then the rendering engine doesn't know what to display, or how.

The preceding XAML is probably the simplest one that will display anything. You'll notice there are a few binding statements; they are binding to properties relative to the ItemsSource. Let's have a look at exactly what it is we're binding to.

TreeView Item model and ItemsSource

In order to bind something to a control in UWP, you need something. Essentially, what that means is that we need a model.


A model, in .NET terms, is simply a class that holds data.

We're going to create a new class, and we'll call it Item:

public class Item
{
    public string Name { get; set; }
    public ObservableCollection<Item> Children { get; set; } = new ObservableCollection<Item>();
    public ItemType ItemType { get; set; }
    public string FullName { get; set; }
 
    public override string ToString()
    {
        return Name;
    }
}
I would always recommend that models are held in their own file and sit in a folder called Models, but there's no technical reason why you couldn't add this class to the end of ImportBooks.cs.

Most of this class should be self-explanatory; we're holding the Name and FullName (that is, the name and path) of the file. The ObservableCollection is a special type of Collection that allows the UI framework to be notified when it changes.


For the code that we're writing here, we could get away with this simply being a List; however, ObservableCollection is good practice when dealing with desktop XAML frameworks such as UWP, and this will make extensibility easier.

Finally, we're holding the type of the item, which is a new enumerated type:

public enum ItemType
{
    Docx,
    Docxx,
    Pdfx,
    Epubx,
    Folder
}

Back in ImportBooks.cs, we're going to set up our ItemsSource. The first step is to add a class-level variable called DataSource:

public ObservableCollection<Models.Item> DataSource { get; set; }

Our next change is in the btnSelectSourceFolder_Click event handler:

private void btnSelectSourceFolder_Click(object sender, EventArgs e)
{
    try
    {
        FolderBrowserDialog fbd = new FolderBrowserDialog();
        fbd.Description = "Select the location of your eBooks and documents";
 
        DialogResult dlgResult = fbd.ShowDialog();
        if (dlgResult == DialogResult.OK)
        {
            UpdateBookList(fbd.SelectedPath);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

As you can see, the new method is hugely simplified compared to the previous version; we've extracted all the real logic into a new method, so let's see that next:

private void UpdateBookList(string path)
{            
    DirectoryInfo di = new DirectoryInfo(path);
    var bookList = new List<Models.Item>();
    var rootItem = new Models.Item()
    {
        Name = di.Name
    };
 
    rootItem.ItemType = Models.ItemType.Folder;
 
    PopulateBookList(di.FullName, rootItem);
    bookList.Add(rootItem);
 
    DataSource = new ObservableCollection<Models.Item>(bookList);
    _tvFoundBooks.ItemsSource = DataSource.OrderBy(a => a.Name);
}

Here, we're setting up the root item of our TreeView; however, you'll notice that the only reference that we actually have to the TreeView is at the end, where we refresh ItemsSource. PopulateBookList is our next port of call. As before, this method is essentially in two parts; let's see the first part:

public void PopulateBookList(string paramDir, Models.Item rootItem)
{
    if (rootItem == null) throw new ArgumentNullException();

rootItem.FullName = paramDir;
rootItem.ItemType = Models.ItemType.Folder; DirectoryInfo dir = new DirectoryInfo(paramDir); foreach (DirectoryInfo dirInfo in dir.GetDirectories()) { var item = new Models.Item(); item.Name = dirInfo.Name; rootItem.Children.Add(item); PopulateBookList(dirInfo.FullName, item); }

Here, we're recursively traversing the directory structure and populating our new model. Notice that we're setting the item type and the FullName (the directory path) at the start, and then we iterate through all the sub-directories, re-calling our method.

Recursion is the practice of calling a method from itself. Is can be very useful in scenarios such as this, where you wish to perform exactly the same operation on nested objects. It is faster than using a loop; however, it does have the potential to fill up the stack very quickly if used incorrectly.

For the second part of the function, we'll process any files that are in the current directory (that is, whichever directory is at the top of the recursion stack at the time):

    foreach (FileInfo fleInfo in dir.GetFiles().Where(x => AllowedExtensions.Contains(x.Extension)).ToList())
    {
        var item = new Models.Item();
        item.Name = fleInfo.Name;
 
        item.FullName = fleInfo.FullName;
        item.ItemType = (Models.ItemType)Enum.Parse(typeof(Extention), fleInfo.Extension.TrimStart('.'), true);
 
        rootItem.Children.Add(item);
    }
}

Our next change is to the ItemInvoked method; the new method should look as follows:

private void _tvFoundBooks_ItemInvoked(Windows.UI.Xaml.Controls.TreeView sender, Windows.UI.Xaml.Controls.TreeViewItemInvokedEventArgs args)
{
    var selectedItem = (Models.Item)args.InvokedItem;
 
    DocumentEngine engine = new DocumentEngine();
    string path = selectedItem.FullName.ToString();
 
    if (File.Exists(path))
    {
        var (dateCreated, dateLastAccessed, fileName, fileExtention, fileLength, hasError) = engine.GetFileProperties(selectedItem.FullName.ToString());
 
        if (!hasError)
        {
            txtFileName.Text = fileName;
            txtExtension.Text = fileExtention;
            dtCreated.Value = dateCreated;
            dtLastAccessed.Value = dateLastAccessed;
            txtFilePath.Text = selectedItem.FullName.ToString();
            txtFileSize.Text = $"{Round(fileLength.ToMegabytes(), 2).ToString()} MB";
        }
    }
}

Again, this is very marginally changed; instead of storing the full filename (with the path) in the node tag property, we're now just referencing the underlying model, so it's much clearer. Our next step is to remove the existing WinForms TreeView control.

Removing the existing TreeView

The following code should be removed from ImportBooks.Designer.cs:

// 
// tvFoundBooks
// 
this.tvFoundBooks.Location = new System.Drawing.Point(12, 41);
this.tvFoundBooks.Name = "tvFoundBooks";
this.tvFoundBooks.Size = new System.Drawing.Size(513, 246);
this.tvFoundBooks.TabIndex = 8;
this.tvFoundBooks.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.tvFoundBooks_AfterSelect);

This will remove the control itself. Later, we'll need to remove the following code that adds the TreeView to the controls collection:

this.Controls.Add(this.tvFoundBooks);

That's it. If you now run the project, you'll see a UWP TreeView control right in the middle of a WinForms application.

Visually different images
CONTINUE READING
83
Tech Concepts
36
Programming languages
73
Tech Tools
Icon Unlimited access to the largest independent learning library in tech of over 8,000 expert-authored tech books and videos.
Icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Icon 50+ new titles added per month and exclusive early access to books as they are being written.
C# 8 and .NET Core 3 Projects Using Azure
You have been reading a chapter from
C# 8 and .NET Core 3 Projects Using Azure - Second Edition
Published in: Dec 2019
Publisher:
ISBN-13: 9781789612080
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Modal Close icon
Modal Close icon