Restrucutring and tutorial WIP.
This commit is contained in:
Родитель
afe2fe927e
Коммит
24b4b831f7
119
README.md
119
README.md
|
@ -11,13 +11,18 @@ For MFractor, developing tools like the Image Wizard or localisation wizard cost
|
|||
Therefore, there are compelling reasons to use Xamarin.Forms to build Visual Studio Mac extensions:
|
||||
|
||||
* XAML is much, much easier to work with than XWT, Visual Studio Macs UI framework. This dramatically lowers the barrier to entry for developing Visual Studio Mac extensions.
|
||||
* Developing in XAML/Xamarin.Forms is much faster than XWT as it has a data-binding engine, we can build UIs declaratively in XAML and it is a very well documented API.
|
||||
* We can use data-binding to save a lot of "glue" code and can also make use of value converters, triggers and behaviours.
|
||||
* With a Xamarin.Forms WPF backend available, user interfaces are reusable in both Visual Studio Mac **and** Visual Studio Windows.
|
||||
* By using XAML to build MFractors UIs, I can use MFractor to build itself; an awesome process of dogfooding to accelerate product development.
|
||||
|
||||
There are huge productivity gains here!
|
||||
|
||||
In the tutorial below, I'll be walking through how we can use Xamarin.Forms inside Visual Studio Mac to build rich user interfaces. To prove that this technique is not just a toy, we'll be building an image asset browser you can use to visually explore images inside a solution. When I built this
|
||||
To prove that this technique is valid for production-ready tooling and is not just a toy, we'll be building an image asset browser you can use to visually explore images inside a solution:
|
||||
|
||||
![The image asset browser allows developers to visually explore the images within projects in their solution](img/image-asset-browser.png)
|
||||
|
||||
|
||||
So, read on to learn how to use Xamarin.Forms inside Visual Studio Mac to build rich user interfaces for your tooling.
|
||||
|
||||
## Using Xamarin.Forms Inside Visual Studio Mac
|
||||
|
||||
|
@ -27,33 +32,127 @@ First things first, you **must** have version 1.4.2 of the Addin Maker installed
|
|||
|
||||
Next, you'll need to create a new Visual Studio Mac extension that is an SDK style project and references the NuGet MonoDevelop.Addins v0.4.4. I've found that the Xamarin.Forms bootstrapping process does not work in Visual Studio Mac extensions that are not SDK style projects.
|
||||
|
||||
If you have an existing extension, you'll need to upgrade your main extensions project to an SDK style project and reference NuGet MonoDevelop.Addins v0.4.4. I've found the best way to do this is
|
||||
|
||||
If you have an existing extension, you'll need to upgrade your main extensions project to an SDK style project and reference NuGet MonoDevelop.Addins v0.4.4. I've found the best way to do this is to create a new extension project within
|
||||
|
||||
|
||||
* Add Xamarin.Forms and Xamarin.Forms.Platform.GTK nugets to the project.
|
||||
|
||||
Next, we need to add Xamarin.Forms into our project.
|
||||
|
||||
Add the following nuget packages into your Visual Studio Mac extension:
|
||||
* Xamarin.Forms
|
||||
Add the following packages into your Visual Studio Mac extension:
|
||||
* Xamarin.Forms.Platform.GTK.
|
||||
|
||||
* Initialise Xamarin.Forms, create a startup command to do so.
|
||||
|
||||
Before we can build any UIs Now we need to startup Xamarin.Forms
|
||||
|
||||
|
||||
**InitXamarinFormsCommand.cs**
|
||||
```
|
||||
public class InitXamarinFormsCommand : CommandHandler
|
||||
{
|
||||
protected override void Run()
|
||||
{
|
||||
Forms.Init();
|
||||
Console.WriteLine("Xamarin.Forms has been initialised");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then in our `Manifest.addin.xml` we insert our `InitXamarinFormsCommand` into the `/MonoDevelop/Ide/StartupHandlers` extension point:
|
||||
|
||||
**Manifest.addin.xml**
|
||||
```
|
||||
<Extension path="/MonoDevelop/Ide/StartupHandlers">
|
||||
<Class class="XamarinFormsUIs.Commands.InitXamarinFormsCommand"/>
|
||||
</Extension>
|
||||
```
|
||||
|
||||
When the IDE opens, the `Run()` method of `InitXamarinFormsCommand` will be invoked.
|
||||
|
||||
In future, Microsoft and the Visual Studio Mac team will need
|
||||
|
||||
* Create our Xamarin.Forms user interface.
|
||||
|
||||
* [ImageAssetBrowserView.xaml](src/XamarinFormsUIs/Views/ImageAssetBrowserView.xaml): Our XAML view
|
||||
* [ImageAssetBrowserViewModel.cs](src/XamarinFormsUIs/ViewModels/ImageAssetBrowserViewModel.cs):
|
||||
|
||||
Let's quickly run through what we have here:
|
||||
|
||||
*
|
||||
|
||||
|
||||
|
||||
|
||||
* Use native embedding to inject the view and viewmodel into a GTK dialog.
|
||||
|
||||
**ImageAssetBrowserWindow.cs&**
|
||||
```
|
||||
public class ImageAssetsWindow : Gtk.Window
|
||||
{
|
||||
public ImageAssetsWindow()
|
||||
: base(Gtk.WindowType.Toplevel)
|
||||
{
|
||||
var page = new ImageAssetBrowserView();
|
||||
|
||||
page.BindingContext = new ImageAssetBrowserViewModel(MonoDevelop.Ide.TypeSystem.TypeSystemService.Workspace.CurrentSolution);
|
||||
|
||||
this.Add(page.CreateContainer());
|
||||
SetDefaultSize((int)page.WidthRequest, (int)page.HeightRequest);
|
||||
SetSizeRequest((int)page.WidthRequest, (int)page.HeightRequest);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This creates a reusable window
|
||||
|
||||
|
||||
|
||||
* Create our Xamarin.Forms .
|
||||
|
||||
* Show our UI using a command handler in the tools menu.
|
||||
|
||||
Lastly, we create a `CommandHandler` to show our user interface:
|
||||
|
||||
**BrowseImageAssetsCommand.cs**
|
||||
```
|
||||
public class BrowseImageAssetsCommand : CommandHandler
|
||||
{
|
||||
protected override void Update(CommandInfo info)
|
||||
{
|
||||
info.Enabled = true;
|
||||
info.Visible = true;
|
||||
}
|
||||
|
||||
protected override void Run()
|
||||
{
|
||||
new ImageAssetsWindow().Show();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then we expose the `BrowseImageAssetsCommand` through the tools menu:
|
||||
|
||||
**Manifest.addin.xml**
|
||||
```
|
||||
<Extension path="/MonoDevelop/Ide/Commands">
|
||||
<Command _label="Browse Image Assets"
|
||||
id="XamarinFormsUIs.Commands.BrowseImageAssetsCommand"
|
||||
description="Allows you to visually explore the image assets in your solution."
|
||||
defaultHandler="XamarinFormsUIs.Commands.BrowseImageAssetsCommand"/>
|
||||
</Extension>
|
||||
|
||||
<Extension path = "/MonoDevelop/Ide/MainMenu/Tools">
|
||||
<CommandItem id="XamarinFormsUIs.Commands.BrowseImageAssetsCommand"/>
|
||||
</Extension>
|
||||
```
|
||||
|
||||
|
||||
## Closing Thoughts
|
||||
|
||||
As demonstrated,
|
||||
## Summary
|
||||
|
||||
|
||||
|
||||
* We need to make an official Visual Studio Mac Xamarin.Forms extension to prevent multiple calls to Forms.Init() and potential assembly version conflicts.
|
||||
*
|
||||
* My
|
||||
|
||||
https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/Barack_Obama_Mic_Drop_2016.jpg/440px-Barack_Obama_Mic_Drop_2016.jpg
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 400 KiB |
До Ширина: | Высота: | Размер: 70 KiB После Ширина: | Высота: | Размер: 70 KiB |
|
@ -1,12 +1,5 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MonoDevelop.Components.Commands;
|
||||
using MonoDevelop.Ide;
|
||||
using Xamarin.Forms.Platform.GTK;
|
||||
using MonoDevelop.Components.Commands;
|
||||
using XamarinFormsUIs.Windows;
|
||||
using Xwt;
|
||||
using Xwt.GtkBackend;
|
||||
|
||||
namespace XamarinFormsUIs.Commands
|
||||
{
|
|
@ -17,7 +17,9 @@ namespace XamarinFormsUIs.Converters
|
|||
|
||||
var input = (string)value;
|
||||
|
||||
return Path.GetFileName(input);
|
||||
var fi = new FileInfo(input);
|
||||
|
||||
return Path.Combine(fi.Directory.Name, fi.Name);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Xamarin.Forms;
|
||||
using XamarinFormsUIs.Converters;
|
||||
|
||||
namespace XamarinFormsUIs.Converters
|
||||
{
|
||||
[ValueConversion(typeof(bool), typeof(bool))]
|
||||
public class InverseBooleanConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool == false)
|
||||
{
|
||||
return default(bool);
|
||||
}
|
||||
|
||||
var input = (bool)value;
|
||||
|
||||
return !input;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using XamarinFormsUIs.ViewModels;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Xamarin.Forms;
|
||||
using XamarinFormsUIs.Helpers;
|
||||
|
||||
namespace XamarinFormsUIs.ViewModels
|
||||
{
|
||||
|
||||
|
||||
public class ImageAssetBrowserViewModel : BaseViewModel
|
||||
{
|
||||
private List<TextDocument> _ProjectImages;
|
||||
private List<TextDocument> _projectImages;
|
||||
public List<TextDocument> ProjectImages
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ProjectImages;
|
||||
return _projectImages;
|
||||
}
|
||||
set
|
||||
{
|
||||
SetProperty(value, ref _ProjectImages);
|
||||
SetProperty(value, ref _projectImages);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +35,8 @@ namespace XamarinFormsUIs.ViewModels
|
|||
this.solution = currentSolution;
|
||||
Projects = solution.Projects.ToList();
|
||||
ProjectNames = Projects.Select(p => p.Name).ToList();
|
||||
ProjectImages = Projects[SelectedProjectIndex].AdditionalDocuments.Where(IsImage).ToList();
|
||||
|
||||
UpdateProjectImagesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private int _selectedProjectIndex = 0;
|
||||
|
@ -50,12 +48,49 @@ namespace XamarinFormsUIs.ViewModels
|
|||
}
|
||||
set
|
||||
{
|
||||
bool changed = value != SelectedProjectIndex;
|
||||
SetProperty(value, ref _selectedProjectIndex);
|
||||
|
||||
_ProjectImages = Projects[SelectedProjectIndex].AdditionalDocuments.Where(IsImage).ToList();
|
||||
if (changed)
|
||||
{
|
||||
UpdateProjectImagesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task<bool> UpdateProjectImagesAsync()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var assets = Projects[SelectedProjectIndex].AdditionalDocuments.Where(IsImage).ToList();
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ProjectImages = assets;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private bool IsImage(TextDocument document)
|
||||
{
|
||||
var extension = Path.GetExtension(document.FilePath);
|
|
@ -2,24 +2,30 @@
|
|||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:behaviours="clr-namespace:XamarinFormsUIs.Behaviours"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="XamarinFormsUIs.Views.ImageAssetBrowserView" xmlns:converters="clr-namespace:XamarinFormsUIs.Converters"
|
||||
WidthRequest="700" HeightRequest="400">
|
||||
x:Class="XamarinFormsUIs.Views.ImageAssetBrowserView"
|
||||
xmlns:converters="clr-namespace:XamarinFormsUIs.Converters"
|
||||
WidthRequest="700"
|
||||
HeightRequest="400">
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<converters:SelectedItemEventArgsToSelectedItemConverter x:Key="selectedItemEventArgsToSelectedItemConverter"/>
|
||||
<converters:FilePathToFileNameConverter x:Key="filePathToFileNameConverter" />
|
||||
<converters:InverseBooleanConverter x:Key="inverseBooleanConverter"/>
|
||||
<converters:SelectedItemEventArgsToSelectedItemConverter x:Key="selectedItemEventArgsToSelectedItemConverter"/>
|
||||
<converters:FilePathToFileNameConverter x:Key="filePathToFileNameConverter" />
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
<ContentPage.Content>
|
||||
<StackLayout Orientation="Vertical">
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Label Text="Project:"/>
|
||||
<Picker ItemsSource="{Binding ProjectNames}"
|
||||
<Label VerticalOptions="Center" Text="Projects:" FontSize="Large" FontAttributes="Bold"/>
|
||||
<Picker VerticalOptions="Center" ItemsSource="{Binding ProjectNames}"
|
||||
SelectedIndex="{Binding SelectedProjectIndex}">
|
||||
</Picker>
|
||||
</StackLayout>
|
||||
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
|
||||
<ListView ItemsSource="{Binding ProjectImages}" WidthRequest="150">
|
||||
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}"/>
|
||||
<ListView ItemsSource="{Binding ProjectImages}"
|
||||
IsVisible="{Binding IsBusy, Converter={StaticResource inverseBooleanConverter}}"
|
||||
WidthRequest="150">
|
||||
<ListView.Behaviors>
|
||||
<behaviours:EventToCommandBehavior EventName="ItemSelected"
|
||||
Command="{Binding OnImageSelected}"/>
|
||||
|
@ -27,14 +33,15 @@
|
|||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ViewCell>
|
||||
<StackLayout Orientation="Horizontal" Spacing="0" Padding="0,10">
|
||||
<StackLayout Orientation="Vertical" Spacing="0" Padding="0,0,0,1">
|
||||
<Label Text="{Binding Name, Converter={StaticResource filePathToFileNameConverter}}" HorizontalOptions="FillAndExpand" VerticalOptions="Center"/>
|
||||
<ContentView HeightRequest="1" HorizontalOptions="FillAndExpand" BackgroundColor="#4E4E4E"/>
|
||||
</StackLayout>
|
||||
</ViewCell>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<StackLayout VerticalOptions="Center">
|
||||
<StackLayout VerticalOptions="Center" HorizontalOptions="End">
|
||||
<Image Source="{Binding SelectedImage}"
|
||||
WidthRequest="300"
|
||||
HeightRequest="300"
|
Загрузка…
Ссылка в новой задаче