📜 ⬆️ ⬇️

WinPhone: The Path to Excellence

Attention!
More recent and progressive materials on MVVM pattern are presented in the article Context Model Pattern via Aero Framework and are discussed in detail in the next series of articles.

In this article, I will talk about how to make a non-complicated, but interactive and functional graphical drawing editor under Windows Phone. I think even experienced developers will be able to find for themselves something interesting and new. A unique feature of the editor will be a story that can literally be unwound for the right moment with the slider slider. And yes, in the end we will draw a rainbow! Go…

Of course, I prepared a qualitative example .
')
image


Markup extensions

When I first started developing on WinPhone, I was almost immediately disappointed by a number of limitations of this platform. For example, it turned out that there are not even the usual markup extensions here, as in WPF or Silverlight. After all, for example, for localization or images it is much more beautiful to write the following code in xaml:

<TextBlock Text={Localizing Hello}/> <Button Content={Picture New.png}/> 

"How so?! It's so convenient thing, ”I thought, and at leisure I decided to explore this question in more detail, and for good reason.

Rummaging through the disassembler from resharper in library classes, I suddenly noticed that the Binding class is not marked with the sealed attribute. And what if to inherit from him, flashed in my head? I tried, and it turned out!

  public abstract class MarkupExtension : Binding, IValueConverter { protected MarkupExtension() { Source = Converter = this; } protected MarkupExtension(object source) // set Source to null for using DataContext { Source = source; Converter = this; } protected MarkupExtension(RelativeSource relativeSource) { RelativeSource = relativeSource; Converter = this; } public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture); public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 

Here is a base class out. And then an example of the implementation of extensions for localization.

  public class Localizing : MarkupExtension { public static readonly LocalizingManager Manager = new LocalizingManager(); public Localizing() { Source = Manager; Path = new PropertyPath("Source"); } public string Key { get; set; } public override string ToString() { return Convert(Manager.Source, null, Key, Thread.CurrentThread.CurrentCulture) as string ?? string.Empty; } public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var key = Key; var resourceManager = value as ResourceManager; if (resourceManager == null || string.IsNullOrEmpty(key)) return ":" + key + ":"; var localizedValue = resourceManager.GetString(key); return localizedValue ?? ":" + key + ":"; } } public class LocalizingManager : INotifyPropertyChanged { private ResourceManager _source; public ResourceManager Source { get { return _source; } set { _source = value; PropertyChanged(this, new PropertyChangedEventArgs("Source")); } } public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { }; } 

Now you can write to WinPhone

  <TextBlock Text="{f:Localizing Key=ApplicationTitle}"/> 

Unfortunately, it was not possible to get rid of the f: prefix due to platform restrictions, it is also necessary to specify the Key property name, but this is better than the standard entry

 <TextBlock Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"/> 

By the way, our implementation supports hot language change while the application is running, which is called on the fly. Using the base class MarkupExtension can do much more interesting things. I recommend for this to see code examples.

Caution against possible pitfall. If you use the MarkupExtension (RelativeSource relativeSource) constructor, then the Convert method in the value parameter will come to the control to which the binding is performed. With the control, you can do anything you want, but if you keep a hard link to it, a situation with a memory leak may occur (for example, if the extension is associated with a static class instance, then part of the interface will be prevented from garbage collection, even if when the view is closed and not needed). Therefore, use lazy links for similar purposes (WeakReferences).

Shared commands (shared commands)

With teams in WinPhone, things are not very good, you need to implement them yourself. Moreover, here, too, you need to be careful, because the control subscribes to the CanExecuteChanged event, and if this subscription is illiterate, you can get all the same memory leaks. I myself did not pay attention to this nuance, but my comrade and excellent developer Yuri Kalinov pointed it out to me, for which I want to thank him. You can read about this issue here .

But WPF has a wonderful mechanism called RoutedCommands and CommandBindings. In the previous article I told how to use it beautifully. And what if you implement something similar on WinPhone? Having dedicated the evening to this task, I did achieve some good results and implemented the concept of shared commands (shared commands). They are not routed through the visual tree, but for the tasks of the application are the best fit. Immediately refer the reader to the example, to see their implementation, here I will tell you how to use them. Everything is simple and convenient, in the view of the model we write something like this

 this[SharedCommands.Back].CanExecute += (sender, args) => args.CanExecute = TouchIndex > 0; this[SharedCommands.Next].CanExecute += (sender, args) => args.CanExecute = TouchIndex < _toches.Count; this[SharedCommands.Back].Executed += (sender, args) => TouchIndex--; this[SharedCommands.Next].Executed += (sender, args) => TouchIndex++; 

And on the presentation about the following

 <Button Command="{f:Command Key=Back}" Content="{f:Picture Key=/Resources/IconSet/Next.png, Width=32, Height=32}"/> <Button Command="{f:Command Key=Next}" Content="{f:Picture Key=/Resources/IconSet/Next.png, Width=32, Height=32}"/> 


Lambda expressions

Notification of properties of a view model through lambda expressions is a classic

  public Tool Tool { get { return Get(() => Tool); } set { Set(() => Tool, value); } } 

It is also very convenient to overload the indexer.

  this[() => Background].PropertyChanged += (sender, args) => { Canvas.Children.Clear(); Canvas.Background = Background; _toches.GetRange(0, TouchIndex).ForEach(Canvas.Children.Add); }; 

If someone else has concerns about the speed of such structures, then I dare to dispel them - everything works quickly. I remember once someone laid out on Habré disassembled source code of the client version of Skype for WinPhone, and then these sources were promptly removed, but I managed to download them and watch how the view-models were arranged there. This is not an indicator, of course, but just the same they used lambda expressions for property notification. I think that Skype was made by smart people from Maykrasoft, so there is some trust in them. And the Skype sources were then deleted from the computer;)

Drawing

We come to the most interesting - drawing. How best to organize it, taking into account the mobility of the platform? In my opinion, it will be easier and more logical to use standard tools, namely to use the primitives Canvas, Polyline and the like. Why I recommend them, not a bike based on, for example, WritableBitmap, because the interface uses a graphics core or processor's SIMD to render the interface, but if we do hand-drawing, we will simply shift the load to the usual instructions of the processor, which will reduce performance and greatly complicate the development.

The essence of the mechanism is as follows. We have a canvas ( Canvas ), which is displayed on the interface, and any touches to it we interpret as brush strokes with a certain brush. For each stroke, a primitive is created, and then it is added to the Canvas.Children collection. But what if we have a thousand of such strokes, will it affect performance? Yes, it will have a significant effect, so you should at least occasionally do rasterization of the image, that is, clear the Canvas.Children and place in the Canvas.Background the drawing that turned out at the moment. It looks like this

  var raster = new WriteableBitmap(Canvas, null); Canvas.Background = new ImageBrush { AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top, Stretch = Stretch.None, ImageSource = raster, }; Canvas.Children.Clear(); 

Rasterization in the example occurs every time before adding a new primitive, but how then can we organize the story, because at rasterization it is lost? Here, too, nothing complicated - just keep the original Background of the canvas, which was before the start of drawing, and get the List _toches . , _toches Canvas.Children , _toches .

: Polyline OpacityMask Canvas. , .

(). , .



, , . , , .

<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>

, – , , . MVVM: . , , , .

!

PS . , , , , . .
collection List _toches . , _toches Canvas.Children , _toches .

: Polyline OpacityMask Canvas. , .

(). , .



, , . , , .

<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>

, – , , . MVVM: . , , , .

!

PS . , , , , . .
List _toches . , _toches Canvas.Children , _toches .

: Polyline OpacityMask Canvas. , .

(). , .



, , . , , .

<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>

, – , , . MVVM: . , , , .

!

PS . , , , , . .
 List _toches    .        ,              _toches  Canvas.Children ,        _toches . 

: Polyline OpacityMask Canvas. , .

(). , .



, , . , , .

<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>

, – , , . MVVM: . , , , .

!

PS . , , , , . .
List _toches . , _toches Canvas.Children , _toches .

: Polyline OpacityMask Canvas. , .

(). , .



, , . , , .

<LinearGradientBrush x:Key="RainbowBrush" StartPoint="0 0" EndPoint="0 0.6"> <LinearGradientBrush.Transform> <ScaleTransform ScaleY="2.8"/> </LinearGradientBrush.Transform> <LinearGradientBrush.GradientStops> <GradientStop Color="#FFFF0000" Offset="0.0"/> <GradientStop Color="#FFFFFF00" Offset="0.1"/> <GradientStop Color="#FF00FF00" Offset="0.2"/> <GradientStop Color="#FF00FFFF" Offset="0.3"/> <GradientStop Color="#FF0000FF" Offset="0.4"/> <GradientStop Color="#FFFF00FF" Offset="0.5"/> <GradientStop Color="#FFFF0000" Offset="0.6"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush>

, – , , . MVVM: . , , , .

!

PS . , , , , . .

Source: https://habr.com/ru/post/210778/


All Articles