Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Friday, November 28, 2008

WPF Bind to a Method

In this part we will bind to a method. As we type a number into a textbox the results will be displayed in label below it. For this example we use a class which figures out the area of a circle.


Let's start off with a class which calculates the area of a circle. This class has one function named CalculateArea.

Public Class Area

Public Function CalculateArea(ByVal Radius As Double) As Double
Return Math.PI * Radius ^ 2
End Function
End Class


Now that we have a class set up to calculate the area we need 2 additonal classes. The first needs to inherit from ValidationRule to alert the user if they enter an incorrect value (textbox's border turns red for invalid values). The other need to inherit from IValueConveter to convert the user input from string to double and back again.


Imports System.Windows.Controls
Imports System.Windows.Data

Public Class ValidDouble
Inherits ValidationRule


Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
Dim num As Double

If Double.TryParse(value.ToString, num) Then
Return New ValidationResult(True, Nothing)
Else
Return New ValidationResult(False, "Invalid Number")
End If
End Function
End Class


Public Class DoubleToString
Implements IValueConverter

Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
If value IsNot Nothing Then
Return value.ToString
Else
Return Nothing
End If
End Function

Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Dim num As Double
If (value IsNot Nothing) AndAlso Double.TryParse(value.ToString, num) Then
Return num
Else
Return Nothing
End If
End Function
End Class

Now that we have the classes we need its time to register the namespaces with the form.


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFBindToMethod"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="WPFBindToMethod" Height="300" Width="300"
>
Finally we will use an ObjectDataProvider to bind to the method and register the ValidationRule with the form.



MethodName="CalculateArea" x:Key="CircleArea">

0







The Form's complete XAML



xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFBindToMethod"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="WPFBindToMethod" Height="300" Width="300"
>

MethodName="CalculateArea" x:Key="CircleArea">

0


















BindsDirectlyToSource="true" UpdateSourceTrigger="PropertyChanged"
Converter="{StaticResource doubleToString}">









WPF OneWay Binding Part 3

In this part we will bind to a class. We will start off by creating a grid to display the properties of the class. Finally we will show how to get the form to update itself when a value changes in the class.


Lets start off creating a class that shows some info about the computer. Here is the class.

Public Class ComputerInfo

Public ReadOnly Property UserName() As String
Get
Return Environment.UserName
End Get
End Property
Public ReadOnly Property ComputerName() As String
Get
Return Environment.MachineName
End Get
End Property
Public ReadOnly Property UpTime() As Integer
Get
Return Environment.TickCount
End Get
End Property


End Class



Ok lets register the class with the form.

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFOneWayBindToVariable"
Title="WPFOneWayBindToVariable" Height="300" Width="300"
>




Now lets create a grid with 2 columns and 3 rows to show the class in.












Lets put the data in the grid we just created.

WPF OneWay Binding Part 2

In the last part we bound a label to the value of the scrollbar. In this part we will format the scrollbar's value before we display it.


To be able to format the output of a bound value you need a class which inherits from IValueConverter. For this example I am using the NumbersToWords function found here to convert the number to a word for display.



Public Class NumberToWord
Implements IValueConverter


Private Function NumbersToWords(ByVal num As Decimal) As String
Dim power_value(5) As Decimal
Dim power_name(5) As String
Dim digits As Integer
Dim result As String
Dim i As Integer

' Initialize the power names and values.
power_name(1) = "trillion" : power_value(1) = 1000000000000.0#
power_name(2) = "billion" : power_value(2) = 1000000000
power_name(3) = "million" : power_value(3) = 1000000
power_name(4) = "thousand" : power_value(4) = 1000
power_name(5) = "" : power_value(5) = 1
For i = 1 To 5
' See if we have digits in this range.
If num >= power_value(i) Then
' Get the digits.
digits = Int(num / power_value(i))
' Add the digits to the result.
If Len(result) > 0 Then result = result & ", "
result = result & Words_1_999(digits) & " " & power_name(i)
' Get the number without these digits.
num = num - digits * power_value(i)
End If
Next i
NumbersToWords = Trim$(result)
End Function

' Return words for this value between 1 and 999.
Private Function Words_1_999(ByVal num As Integer) As String
Dim hundreds As Integer
Dim remainder As Integer
Dim result As String
hundreds = num \ 100
remainder = num - hundreds * 100
If hundreds > 0 Then
result = Words_1_19(hundreds) & " hundred "
End If
If remainder > 0 Then
result = result & Words_1_99(remainder)
End If
Words_1_999 = Trim$(result)
End Function

' Return a word for this value between 1 and 19.
Private Function Words_1_19(ByVal num As Integer) As String
Select Case num
Case 1
Words_1_19 = "one"
Case 2
Words_1_19 = "two"
Case 3
Words_1_19 = "three"
Case 4
Words_1_19 = "four"
Case 5
Words_1_19 = "five"
Case 6
Words_1_19 = "six"
Case 7
Words_1_19 = "seven"
Case 8
Words_1_19 = "eight"
Case 9
Words_1_19 = "nine"
Case 10
Words_1_19 = "ten"
Case 11
Words_1_19 = "eleven"
Case 12
Words_1_19 = "twelve"
Case 13
Words_1_19 = "thirteen"
Case 14
Words_1_19 = "fourteen"
Case 15
Words_1_19 = "fifteen"
Case 16
Words_1_19 = "sixteen"
Case 17
Words_1_19 = "seventeen"
Case 18
Words_1_19 = "eightteen"
Case 19
Words_1_19 = "nineteen"
Case Else
Words_1_19 = ""
End Select
End Function

' Return a word for this value between 1 and 99.
Private Function Words_1_99(ByVal num As Integer) As String
Dim result As String
Dim tens As Integer
tens = num \ 10
If tens <= 1 Then ' 1 <= num <= 19
result = result & " " & Words_1_19(num)
Else
' 20 <= num
' Get the tens digit word.
Select Case tens
Case 2
result = "twenty"
Case 3
result = "thirty"
Case 4
result = "forty"
Case 5
result = "fifty"
Case 6
result = "sixty"
Case 7
result = "seventy"
Case 8
result = "eighty"
Case 9
result = "ninety"
End Select
' Add the ones digit number.
result = result & " " & Words_1_19(num - tens * 10)
End If
Words_1_99 = Trim$(result)
End Function

Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
Dim d As Double
If Double.TryParse(value.ToString, d) Then
Return NumbersToWords(CDec(d))
Else
Return value
End If
End Function

Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
Return Nothing
End Function
End Class

Now that we created the class we need to register the xml namespace with the window. The namespace you need to use for this is clr-namespace:NamespaceName. In this case it is the applications name. I am adding the local prefix so we can reference it later.


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFOneWayBind"
Title="WPFOneWayBind" Height="300" Width="300"
>

Now we need to make the converter a resource for the window.







Right after we set the path for the binding we can specify the converter. The converter is a StaticResource of the window.


WPF OneWay Binding Part 1

Windows Presentation Foundation (WPF) foundation is for building applications and experiences in Windows Vista that blend the application UI, documents, and media content. In this series of blog entries we willl start to explore some of the improvements to data binding in WPF. You will need to have the .Net Framework 3.0, Windows Vista SDK, and Visual Studio 2005 extensions for WPF and WCF installed for this sample.


For this example we will start off with a Windows Application (WPF). In the forms XMAL we will add a stack panel to hold a label and scrollbar. The label's content will be bound to the scrollbar's value so as we move the scrollbar the value will be displayed in the label.


In the Lets set up a DataContext which is bound to the scrollbar .


WPF: Bind to a ListView

There is not a DataGrid or DataGridView in Windows Presentation Foundation. Use the ListView to display data in a table. In the window's Xaml define the listview and the fields you want displayed. The code behind file is where you get the data from the Northwind database and bind the listview.

The Windows Xaml

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF1" Height="300" Width="300"
>













The Code Behind

Imports System.Data

' Interaction logic for Window1.xaml
Partial Public Class Window1
Inherits System.Windows.Window

Public Sub New()
InitializeComponent()
Dim dt As New DataTable

Dim strConn As String = _
"Server = .\sqlexpress;Database = NorthWind; Integrated Security = SSPI;"
Dim conn As New SqlConnection(strConn)
Dim da As New SqlDataAdapter("Select LastName, FirstName from Employees", conn)
da.Fill(dt)
ListView1.DataContext = dt
Dim bind As New Binding
ListView1.SetBinding(ListView.ItemsSourceProperty, bind)
End Sub

End Class

WPF Tips'n'Tricks #1: Have all your dates, times, numbers... in the local culture

Technorati tags: , ,

one trick a day you'll find useful in your .net 3 development life. Some of it I came up with myself, some of it comes from the forums. I'll make sure to give credit where credit is due :)

WPF has a very annoying tendency. All dates are by default in the en-US format. You need to understand first why, and then I'll give a few potential solutions, and one that is now my favorite.

You'll notice that UIElement has a property called Language. This property is supposed to define what language the element has been written in. That way, WPF can know when some content is en-US (American English), or en-GB (British English), etc. It is also bound to the xml:lang property you can set on any xml document.

Now if you read the documentation the way I did, you realize that xml:lang by *default* is set to the empty string, and as such doesn't have any associated culture. But by default, the matching Language property has a default of... en-US! And because of the way bindings work, that's why all your dates will always show up in American English format.

Whenever you bind an element's property to a DateTime object, the binding is going to covert the DateTime to the type of the property you're binding to. Take for example the following binding:

<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:sys="clr-namespace:System;assembly=mscorlib">

<Grid>

<TextBlock Text="{Binding Path={x:Static sys:DateTime.Now}}" />

Grid>

Window>

This will convert from DateTime to a String (the type of the Text property). But to do so, it is going to use a default converter. If you check the documentation for bindings, you'll see that you can specify an IValueConverter in the Converter property. And you'll also see a ConverterCulture property that specify which culture you want to use to convert your data, here our DateTime object.

As I said before, without a Converter specified, WPF will find one automatically for you, either built-in ones or using the existing conversion infrastructure that has been around for now three versions. But guess what happens when you don't specify a ConverterCulture?

WPF selects the culture of the Language property of the element on which the binding is applied. In our case, the default: en-US again!

So here's my trick of the day. To ensure your application defaults to the current culture *on the client machine* and *at run time*, you can add one simple line in your App.xaml.cs.

public partial class App : Application

{

static App()

{

FrameworkElement.LanguageProperty.OverrideMetadata(

typeof(FrameworkElement),

new FrameworkPropertyMetadata(

XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));

}

}

What this bit of code does is override the default value of the Language property on all the FrameworkElement inherited classes in your application to the CurrentCulture of the computer you run on. In this case, we're interested in overriding this value so that any content is assumed by default to be in the language associated with the way you want your localization to be made. This is quite wrong, as explained by Michael in his Why we have both CurrentCulture and CurrentUICulture post, but it's the solution approaching the most an acceptable outcome.

As for what Microsoft probably should have done? Set by default the Binding.ConverterCulture property to the CultureInfo.CurrentCulture value. But that's just my opinion :)