
Proper handling of XAML resources
Hi, Habr.
About a week ago I read the article “How to get convenient access to XAML-resources from Code-Behind” and was not a little surprised. I apologize in advance to EBCEu4 , the author of the aforementioned article, because I am going to criticize the approach presented by him a little.
I want to note that the article contains only recommendations for the proper use of resources and does not purport to be complete. My article will consist of three points. In the first I’ll give an example of a situation where the above approach is justified, in the second I’ll try to explain why it’s wrong to pull resources from the XAML markup into code-behind, in the third I’ll try to give an example of code that helps to avoid such actions.
Let's take a closer look at the article I pointed out and see what’s the matter.
The indicated material ended with a call to download the script and use it in your projects, but the author did not bother to give an example of a situation where such an approach is justified (as quite rightly noted in the comments). In favor of this approach, I can give only one example. Suppose for a moment that you are creating an application, one of the pages of which includes a list of users. You made a beautiful template for displaying a user, for example, like this: a user's photo (with the necessary size and scaling), name / nickname, skype / phone number, and, of course, status - offline or online.
We won’t see any problems on the computer - the benefit of resources is enough. But consider a situation where the list includes several thousand users, and in your hands you have a low-end device running WinPhone8 / 8.1. Here, obviously, performance problems will begin. ListView will stupid when scrolling, artifacts will arise, and virtualization will not save either. And if in the Universal App you can try to optimize performance using ContainerContentChanging, then in a Silverlight application this will not work (there simply isn’t such a thing).
But in such situations, such an approach is justified: you can abandon the binders that spoil the whole raspberry, and directly “feed” colors and other resources to the controls / items in the sheet, etc. And then, looking ahead, when using MVVM and / or Dependency Injections, the game may not be worth the candle, which means we get bad-practice in our project, which can lead to complication of validation of the final product in the store.
And now - closer to thewall to the point.
Firstly, the approach itself surprised me. Why,for heaven’s sake, except in the case described in the first paragraph, pull resources from their native environment (XAML markup) into code-behind and get an additional chance to get confused in them? I personally think such a practice is simply criminal and perverse. And I’m going to “poke a finger” a little in places of such an approach that are weak in my opinion.
So:
If there was a sudden need to pull resources into code-behind, this, gentlemen and ladies, is a crutch that is a sign of either poor architecture, or poorly written controls / resources, or both.
Imagine the situation (even for a minute) that your project shotyou in the footand you get income. But then a year passes and the guys from Microsoft at the annual event promise the introduction of a new ecosystem, or at least a change in the existing one. What do we get? That's right, a high probability of a crash as a result of trying to stretch a renamed, for example, system color or the margin property (yes, at least anything, in principle). Accordingly, you have to climb into the forgotten code again and pick up a file to redo a bunch of everything. Not a good prospect, is it?
Development problem. If you work hard on something resembling Enterprise, then most likely you are using the MVVM pattern and Dependency Injections. In this case, it’s still more fun, because with MVVM you will first have to pull the resource into your VM, and then bind it to the control on the view: there is no profit in the performance, and the crutch is it, darling! With DI, porridge is brewed even steeper. Suppose you, having done nothing wrong, made your service out of the XAML parser, and pull it on a certain amount of VM. Then the parser itself will put the pig for you, because xpath is not the fastest thing, unfortunately. And if you have a lot of calls to such a service, you'd better not write it at all. I’ll say from myself that if I tried to use a similar approach on the current project (with the exception of the situation from the example), I would have gotten it on hand.
Problem with testing. In the mentioned case of Enterprise, 100% of the tests will be written or, even better, you will use TDD during development. How do you order to cover such a parser with tests?
Well, whoever criticizes is obliged to provide an alternative solution. I offer you my way, which allows you to fully control the presentation of your product without unnecessary crutches and bicycles. Yes, there will be more code, but less headache. Meet:
For example, depending on the user status received from the server (offline / online), you need to change the corresponding text in the list item.
The code:
ConvertBack is not implemented simply as unnecessary.
UserState is our enum for 2 values (Online / Offline), and StringResources.Online/Offline are the corresponding localized resources (strings).
Application of such a converter:
Register a namespace with converters:
Register our converter for user status:
Create a template for the user list item.
Here you are. Now we see the state of this or that user in our list. Just like a string, the converter can return a property of the Visibility type or something else, right up to the DataTemplate (at least there is one more tasty thing for templates). Let's move on to the XAML resources themselves.
Control States - Control States.
With the help of states, you can do with control everything that your heart desires, with just a few lines of code! Suppose that while the user is active, the item in the list has a light background, and when inactive, it becomes darker. To achieve this, the list element should be a control - here the DataTemplate will be replaced by a ControlTemplate - and all of its (control) states should be described in the ControlTemplate.
For example:
Total, markup control is ready. Now let's see, depending on what and how we will change our states. We must make a property for our control, to which we will bind the user state received from the server in our VM, and from which we will push further:
As you can see, we want to change the UserStateProperty property to call the OnUserStatePropertyChanged method. It might look like this:
The ChangeVisualState method will look like this:
Now our control will calmly change its color depending on the value obtained.
Sweet - for dessert.
In addition to such commonplace things as converters and states, there is such a thing as ContentControl. And so it allows you to do a lot. Although, in principle, the approach is brazenly pulled by the converter, it is significantly expanded using the nature of ContentControl itself. Take a look:
The base class for the DataTemplateSelector (there can be many concrete implementations, right?):
And a specific example for our user:
Ta-a-ak, the class is ready for its needs. Now let's play with XAML.
Declare a namespace with our control - the selector.
We will write our additional templates that will correspond to different states of control:
Where StatusOfflineBrush and StatusOnlineBrush are abstract brushes, which we, if necessary, initialized the bi above in the XAML markup.
And slightly change the DataTemplate for the user from step number 1:
Well, I am waiting forthunder and lightning for constructive criticism on my own head.
Thank you all in advance.
About a week ago I read the article “How to get convenient access to XAML-resources from Code-Behind” and was not a little surprised. I apologize in advance to EBCEu4 , the author of the aforementioned article, because I am going to criticize the approach presented by him a little.
I want to note that the article contains only recommendations for the proper use of resources and does not purport to be complete. My article will consist of three points. In the first I’ll give an example of a situation where the above approach is justified, in the second I’ll try to explain why it’s wrong to pull resources from the XAML markup into code-behind, in the third I’ll try to give an example of code that helps to avoid such actions.
Point 1. Lawyer
Let's take a closer look at the article I pointed out and see what’s the matter.
The indicated material ended with a call to download the script and use it in your projects, but the author did not bother to give an example of a situation where such an approach is justified (as quite rightly noted in the comments). In favor of this approach, I can give only one example. Suppose for a moment that you are creating an application, one of the pages of which includes a list of users. You made a beautiful template for displaying a user, for example, like this: a user's photo (with the necessary size and scaling), name / nickname, skype / phone number, and, of course, status - offline or online.
We won’t see any problems on the computer - the benefit of resources is enough. But consider a situation where the list includes several thousand users, and in your hands you have a low-end device running WinPhone8 / 8.1. Here, obviously, performance problems will begin. ListView will stupid when scrolling, artifacts will arise, and virtualization will not save either. And if in the Universal App you can try to optimize performance using ContainerContentChanging, then in a Silverlight application this will not work (there simply isn’t such a thing).
But in such situations, such an approach is justified: you can abandon the binders that spoil the whole raspberry, and directly “feed” colors and other resources to the controls / items in the sheet, etc. And then, looking ahead, when using MVVM and / or Dependency Injections, the game may not be worth the candle, which means we get bad-practice in our project, which can lead to complication of validation of the final product in the store.
Item 2. The prosecutor
And now - closer to the
Firstly, the approach itself surprised me. Why,
So:
If there was a sudden need to pull resources into code-behind, this, gentlemen and ladies, is a crutch that is a sign of either poor architecture, or poorly written controls / resources, or both.
Imagine the situation (even for a minute) that your project shot
Development problem. If you work hard on something resembling Enterprise, then most likely you are using the MVVM pattern and Dependency Injections. In this case, it’s still more fun, because with MVVM you will first have to pull the resource into your VM, and then bind it to the control on the view: there is no profit in the performance, and the crutch is it, darling! With DI, porridge is brewed even steeper. Suppose you, having done nothing wrong, made your service out of the XAML parser, and pull it on a certain amount of VM. Then the parser itself will put the pig for you, because xpath is not the fastest thing, unfortunately. And if you have a lot of calls to such a service, you'd better not write it at all. I’ll say from myself that if I tried to use a similar approach on the current project (with the exception of the situation from the example), I would have gotten it on hand.
Problem with testing. In the mentioned case of Enterprise, 100% of the tests will be written or, even better, you will use TDD during development. How do you order to cover such a parser with tests?
Point 3. But what to do?
Well, whoever criticizes is obliged to provide an alternative solution. I offer you my way, which allows you to fully control the presentation of your product without unnecessary crutches and bicycles. Yes, there will be more code, but less headache. Meet:
Converters
For example, depending on the user status received from the server (offline / online), you need to change the corresponding text in the list item.
The code:
public class UserStateToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is UserState)
{
switch ((UserState)value)
{
case UserState.Online:
return StringResources.Online;
case UserState.Offline:
return StringResources.Offline;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
ConvertBack is not implemented simply as unnecessary.
UserState is our enum for 2 values (Online / Offline), and StringResources.Online/Offline are the corresponding localized resources (strings).
Application of such a converter:
Register a namespace with converters:
xmlns:converters="clr-namespace:MyApp.Converters"
Register our converter for user status:
Create a template for the user list item.
...тут у нас аватар
Header="{Binding UserState, Converter={StaticResource UserStateToStringConverter}}"
...а тут что-нибудь еще, не принципиально что именно.
Here you are. Now we see the state of this or that user in our list. Just like a string, the converter can return a property of the Visibility type or something else, right up to the DataTemplate (at least there is one more tasty thing for templates). Let's move on to the XAML resources themselves.
Control States - Control States.
With the help of states, you can do with control everything that your heart desires, with just a few lines of code! Suppose that while the user is active, the item in the list has a light background, and when inactive, it becomes darker. To achieve this, the list element should be a control - here the DataTemplate will be replaced by a ControlTemplate - and all of its (control) states should be described in the ControlTemplate.
For example:
......тут сам темплейт, к которому применяются состояния.
Total, markup control is ready. Now let's see, depending on what and how we will change our states. We must make a property for our control, to which we will bind the user state received from the server in our VM, and from which we will push further:
public static readonly DependencyProperty UserStateProperty =
DependencyProperty.Register("IndentAngle", typeof(UserState), typeof(UserControl),
new PropertyMetadata(OnUserStatePropertyChanged));
As you can see, we want to change the UserStateProperty property to call the OnUserStatePropertyChanged method. It might look like this:
public static void OnProgressControlPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl sender = d as UserControl;
if(sender != null)
{
sender.ChangeVisualState((UserState)e.NewValue);
}
}
The ChangeVisualState method will look like this:
private void ChangeVisualState(UserState newState)
{
switch (newState)
{
case UserState.Offline:
VisualStateManager.GoToState(this, "OfflineState", false);
break;
case UserState.Online:
VisualStateManager.GoToState(this, "OnlineState", false);
break;
}
}
Now our control will calmly change its color depending on the value obtained.
Sweet - for dessert.
In addition to such commonplace things as converters and states, there is such a thing as ContentControl. And so it allows you to do a lot. Although, in principle, the approach is brazenly pulled by the converter, it is significantly expanded using the nature of ContentControl itself. Take a look:
The base class for the DataTemplateSelector (there can be many concrete implementations, right?):
public abstract class DataTemplateSelector : ContentControl
{
protected abstract DataTemplate GetTemplate(object item, DependencyObject container);
protected override void OnContentChanged(object oldValue, object newValue)
{
base.OnContentChanged(oldValue, newValue);
ContentTemplate = GetTemplate(newValue, this);
}
}
And a specific example for our user:
public class UserStateTemplateControl : DataTemplateSelector
{
public DataTemplate UserOnlineTemplate { get; set; }
public DataTemplate UserOfflineTemplate { get; set; }
protected override DataTemplate GetTemplate(object item, DependencyObject container)
{
UserState state = UserState.Offline;
if (item != null && item is UserState)
{
state = (UserState)item;
}
switch (state)
{
case CategoryProgressStatus.Offline:
return UserOnlineTemplate;
case CategoryProgressStatus.Online:
return UserOfflineTemplate;
}
return null;
}
}
Ta-a-ak, the class is ready for its needs. Now let's play with XAML.
Declare a namespace with our control - the selector.
xmlns:controls="clr-namespace:MyApp.Controls;assembly=MyApp.Controls"
We will write our additional templates that will correspond to different states of control:
Where StatusOfflineBrush and StatusOnlineBrush are abstract brushes, which we, if necessary, initialized the bi above in the XAML markup.
And slightly change the DataTemplate for the user from step number 1:
...тут все еще аватарка
...а тут остальная часть темплейта.
Well, I am waiting for
Thank you all in advance.