January 6 2009, 11:21
Categories: Programming | English Tags: wpf | xaml | expression blend
A while back I wrote a WPF application in which I wanted to bind several things to the value of a Slider control. In this case, I wanted the Slider to control both the size and the opacity of an Image, so that when the user moved the slider from right to left, the Image would become smaller and fade out of view. The problem was of course that the value of the Slider could not be made to directly correspond to the to the value of both the size and the opacity of the Image, because those values were on a different scale.
To put this into more concrete terms, the opacity of the Image must be specified as a value between zero and one, while I wanted the size of the Image to vary between 100 and 500 pixels (both width and height). So, no matter what I use as the Minimum and Maximum values of the Slider, there is no way I can directly bind both the opacity and the size of the Image to the value of the Slider. I needed some way to translate the value of the Slider onto a different scale for each of the bindings.
I quickly realized this was a general problem, looked through the WPF documentation and did some googling on the subject, came up empty and therefore, as we programmers like to do, set out to build a generic solution. Now, I don’t consider myself a WPF expert, and I suspect there might be a simpler way to do this. If there is, I’d love to hear about it. Nonetheless, here is a brief run-down of what I did in case it might help somebody facing the same problem.
IValueConverter to The Rescue
In WPF data binding, there is the concept of a value converter, which is a mechanism by which we can write custom code to convert values between the source and target end of a binding. This mechanism is implemented by means of the IValueConverter interface and the ValueConversionAttribute.
Here’s a few words form the documentation:
If you want to associate a value converter with a binding, create a class that implements the IValueConverter interface and then implement the Convert and ConvertBack methods. Converters can change data from one type to another, translate data based on cultural information, or modify other aspects of the presentation.
There are a few built-in converter classes in WPF which implement the IValueConverter interface:
AlternationConverter BooleanToVisibilityConverter ZoomPercentageConverter JournalEntryListConverter
None of them are applicable to our scenario. Clearly, we need another type of converter that will allow us to do arbitrary translation of values fron one scale onto another, and that will allow us to specify the source and destination scales on a per-binding basis.
A Reusable Translator Class
So, let’s create a class that implements IValueConverter and that maps the source value of the binding on one scale to the destination value of the binding on another scale. Let’s call the class Translator for simplicity. To begin with, here’s code for Translator in its entirety:
1: Imports System.Globalization
2: Imports System.Windows.Data
3:
4:
5: <ValueConversion(GetType(Double), GetType(Double))> _
6: Public Class Translator
7: Implements IValueConverter
8:
9:
10: Sub New()
11: m_SrcMin = 0
12: m_SrcMax = 1
13: m_DstMin = 0
14: m_DstMax = 1
15: End Sub
16:
17:
18: Sub New(ByVal srcMin As Double, ByVal srcMax As Double, _
19: ByVal dstMin As Double, ByVal dstMax As Double)
20: m_SrcMin = srcMin
21: m_SrcMax = srcMax
22: m_DstMin = dstMin
23: m_DstMax = dstMax
24: End Sub
25:
26:
27: Private m_SrcMin As Double
28: Private m_SrcMax As Double
29: Private m_DstMin As Double
30: Private m_DstMax As Double
31:
32:
33: Public Property SrcMin() As Double
34: Get
35: Return m_SrcMin
36: End Get
37: Set(ByVal value As Double)
38: m_SrcMin = value
39: End Set
40: End Property
41:
42:
43: Public Property SrcMax() As Double
44: Get
45: Return m_SrcMax
46: End Get
47: Set(ByVal value As Double)
48: m_SrcMax = value
49: End Set
50: End Property
51:
52:
53: Public Property DstMin() As Double
54: Get
55: Return m_DstMin
56: End Get
57: Set(ByVal value As Double)
58: m_DstMin = value
59: End Set
60: End Property
61:
62:
63: Public Property DstMax() As Double
64: Get
65: Return m_DstMax
66: End Get
67: Set(ByVal value As Double)
68: m_DstMax = value
69: End Set
70: End Property
71:
72:
73: Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, _
74: ByVal parameter As Object, ByVal culture As CultureInfo) _
75: As Object Implements IValueConverter.Convert
76: Dim src As Double = CDbl(value)
77: Dim dst As Double
78: If src < m_SrcMin Then
79: dst = m_DstMin
80: ElseIf src > m_SrcMax Then
81: dst = m_DstMax
82: Else
83: dst = (src - m_SrcMin) / (m_SrcMax - m_SrcMin) * (m_DstMax - m_DstMin) + m_DstMin
84: End If
85: Return dst
86: End Function
87:
88:
89: Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, _
90: ByVal parameter As Object, ByVal culture As CultureInfo) _
91: As Object Implements IValueConverter.ConvertBack
92: Dim dst As Double = CDbl(value)
93: Dim src As Double
94: If dst < m_DstMin Then
95: src = m_SrcMin
96: ElseIf dst > m_DstMax Then
97: src = m_SrcMax
98: Else
99: src = (dst - m_DstMin) / (m_DstMax - m_DstMin) * (m_SrcMax - m_SrcMin) + m_SrcMin
100: End If
101: Return src
102: End Function
103:
104:
105: End Class
Now let’s walk though this code from the top.
First of all, Translator needs to be decorated with the ValueConversionAttribute to let WPF and designer tools such as Expression Blend know what specific types this converter converts between. Ideally, Translator would be a generic class where the type of the value could be specified as a type parameter, but there are a number of reasons why this is difficult to achieve in this case:
- Instantiating a generic type from XAML markup is not possible, at least to my knowledge.
- The type parameter would need to be specified in the
ValueConversionAttribute above our class but .NET does not allow for such a construct.
- There is no mechanism by which we can limit the type parameter to numeric types on which the arithmetics in our code can be performed.
So, we are limited to using System.Double as the type on which Translator can operate. This is fine, because most numeric properties in WPF that we might need to translate in this manner are indeed exposed as System.Double.
Second, we need some properties and constructors. The source and destination scales can be specified using their respective min and max values, so besides implementing the methods of IValueConverter, our class needs 4 properties. The default constructor initializes those with reasonable (but mostly useless) default values. We also provide a constructor where these values can be specified. This is convenient when you want to instantiate Translator from code instead of markup.
Third, we need to implement the Convert and ConvertBack methods of IValueConverter to perform the actual translation. One thing to note here is that this code also truncates the source and destination values to be within the minimum and maximum values specified for their respective scales. This is something you might want to do differently in your own implementation, opting instead to allow linear projection of the values outside the specified minimum and maximum.
Using Translator from XAML
Allright, now that we have our Translator, let’s put it to some use in XAML. First of all we need to declare an XML namespace that maps to whatever CLR namespace in which Translator happens to reside. In my case it lives in the namespace Perceptible.Utils.Wpf, so I add the following namespace declaration to my Window:
1: <Window
2: ...
3: xmlns:utils="clr-namespace:Perceptible.Utils.Wpf">
Next we need to create a couple of instances of Translator to do the translation for us. Remember, in my case I want to bind the size and opacity of an Image to the value of a Slider, so I need two Translator instances each with different destination scales. The easiest way to create those is to add them to the resource collection of the Window:
1: <Window.Resources>
2: <utils:Translator x:Key="OpacityTranslator" SrcMin="0" SrcMax="1" DstMin="0" DstMax="1"/>
3: <utils:Translator x:Key="SizeTranslator" SrcMin="0" SrcMax="1" DstMin="100" DstMax="500"/>
4: </Window.Resources>
Finally, to tie it all together, we just need to specify that these Translator instances be used as value converters in our bindings. We want to bind the Opacity, Width and Height properties of the Image element to the Value property of the Slider, but using different value translation. If you’re using Expression Blend there’s nice GUI support to do this, but in case you’re not here’s the appropriate markup to do the binding (all the other attributes of the Image element are omitted for clarity):
1: <Slider x:Name="FadeSlider" Minimum="0" Maximum="1" Value="1" />
2: <Image Opacity="{Binding Path=Value, Converter={StaticResource OpacityTranslator}, ElementName=FadeSlider, Mode=Default}"
3: Width="{Binding Path=Value, Converter={StaticResource SizeTranslator}, ElementName=FadeSlider, Mode=Default}"
4: Height="{Binding Path=Value, Converter={StaticResource SizeTranslator}, ElementName=FadeSlider, Mode=Default}" />
That should be it. Hope someone out there finds this useful! And like I said, if you know of another way to accomplish this, I would love to hear about it, so leave a comment or drop me a message.
Rate this post
Currently rated 3.8 by 4 people
- Currently 3.75/5 Stars.
- 1
- 2
- 3
- 4
- 5
January 2 2009, 06:27
Categories: English | Programming | Life Tags: blog
So here I am. 05:41 in the morning of the second day of 2009. After almost a year of thinking, visualizing, planning, designing, coding, testing and tweaking (the past 8 days of which have been the most extreme) I am finally ready to re-launch my blog! I made a half-hearted attempt at it about a year ago, tried out various blog engines and themes, but quickly realized I wanted (had to have!) my very own unique look. As it turned out, it took me more than a year to turn that idea into reality.
Thus – welcome to my blog!
For a few years now, I’ve been saying to friends: “I really should be blogging.” Two kinds of things frequently pop into my brain that makes me want to share my thoughts with the rest of the world:
- Complaints on the state of things. I am constantly bothered by all the inefficiency and stupidity that I observe on different levels of society. Mostly it’s just these really practical everyday things, like how stupid it is that we carry around so many pieces of plastic in our wallets when one should suffice (the topic of an upcoming post, no doubt). It’s strange because I don’t consider myself an extremely opinionated guy, and yet so often I get so frustrated with such obvious inadequacy.
- Results from venturing into unknown programming territory. It’s not very often that, as a developer or IT professional I encounter a problem that no one else seems to have reported a solution to (at least one that Google knows about). But it does happen. And when it happens the result is usually anywhere from a couple of hours to several days of research and troubleshooting before finally coming up with a solution. And as a good citizen of the developer ecosystem, I of course feel the overwhelming need to share it with my peers so that anyone facing the same problem now or at some point in the future may benefit from my pioneering efforts. You know, give something back for a change.
Now I have a forum in which I can express these things without further delay!
So those two kinds of things are mainly what I will be posting. There will be other things too, I’m sure. We’ll just have to see what enters my brain. It’s quite conceivable that I might be posting both in English and Swedish (the latter of which is my native tongue), and I will maintain separate categories for both languages so that you may select to subscribe only to one of them if you like.
I also plan to make this blog a place where I can host various articles and pieces of software I plan to publish in the near future. I have so much stuff just sitting around for far too long now because I didn’t feel I had an adequate place to make it available for your perusal. Well, I guess this morning marks the end of that. Damn, I am so excited!!! :)
Before I go, a few words about the technological aspects of this blog. It is running on BlogEngine.NET 1.4.5, and the graphical design is implemented as a theme. There are some features of the theme I’m particularly happy with:
- Complete separation of markup and style (with a few exceptions imposed by the engine emitting crappy markup).
- Completely flexible and “stretchable” layout.
- Heavy use of PNG transparency. This should be obvious for instance in the way the illustration and green-to-black gradient in the header interact when you change the width of the browser window.
Speaking of the illustration, some folks have already asked me what meaning it is supposed to convey. I want to leave this up to each individual beholder to decide (you know, just like the kindergarten lady used to say about abstract art). Maybe it symbolizes the general area of human-machine interaction, a subject to which this blog is partly devoted. Perhaps it represents my frustrations in trying to make these damn machines do what I want them to do. Hell, maybe it even accurately depicts the feelings of the various women who have tried their best to love me over the years.
Leave a comment and tell me what you see. I’m off to bed now.
Rate this post
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5