Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 102 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
102
Dung lượng
864,68 KB
Nội dung
The last sample program in this chapter is called QuickNotes. It is intended to provide a quick way to type some notes and be assured that they’ll be retained without any explicit saving or loading. It’s basically a Windows Phone 7 version of Notepad but only capable of working with a single file. The program also allows changing the font size, so the QuickNotesSettings class for the program’s application settings has two public properties named Text and FontSize, plus methods to save and load those properties in isolated storage: Silverlight Project: QuickNotes File: QuickNotesSettings.cs public class QuickNotesSettings { public QuickNotesSettings() { this.Text = ""; this.FontSize = (double)Application.Current.Resources["PhoneFontSizeMediumLarge"]; } public string Text { set; get; } public double FontSize { set; get; } public static QuickNotesSettings Load() { IsolatedStorageSettings isoSettings = IsolatedStorageSettings.ApplicationSettings; QuickNotesSettings settings; if (!isoSettings.TryGetValue<QuickNotesSettings>("settings", out settings)) settings = new QuickNotesSettings(); return settings; } public void Save() { IsolatedStorageSettings isoSettings = IsolatedStorageSettings.ApplicationSettings; isoSettings["settings"] = this; } } As with the Jot program, these setting are saved, loaded, and exposed in the App class: Silverlight Project: QuickNotes File: App.xaml.cs public partial class App : Application { // Application settings 291 public QuickNotesSettings AppSettings { set; get; } … private void Application_Launching(object sender, LaunchingEventArgs e) { AppSettings = QuickNotesSettings.Load(); } private void Application_Activated(object sender, ActivatedEventArgs e) { AppSettings = QuickNotesSettings.Load(); } private void Application_Deactivated(object sender, DeactivatedEventArgs e) { AppSettings.Save(); } private void Application_Closing(object sender, ClosingEventArgs e) { AppSettings.Save(); } … } The XAML file creates a multiline TextBox the size of the content area. Besides setting TextWrapping for multiline editing, the markup also sets AcceptsReturn to true so that the Enter key will go to a new line, which I thought was appropriate for this program. (In the context of a dialog box, you usually want the Enter key to instead invoke the OK button, even if a TextBox is currently getting input from the user.) Silverlight Project: QuickNotes File: MainPage.xaml <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBox Name="txtbox" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" TextChanged="OnTextBoxTextChanged" /> </Grid> The XAML file also contains an ApplicationBar with two buttons I designed myself for increasing and decreasing the size of the font: Silverlight Project: QuickNotes File: MainPage.xaml <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar> 292 <shell:ApplicationBarIconButton IconUri="/Images/littleletter.icon.png" Text="smaller font" Click="OnAppBarSmallerFontClick" /> <shell:ApplicationBarIconButton IconUri="/Images/bigletter.icon.png" Text="larger font" Click="OnAppBarLargerFontClick" /> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar> With all this preparation, the actual code file for MainPage is rather short and straightforward: Silverlight Project: QuickNotes File: MainPage.xaml.cs public partial class MainPage : PhoneApplicationPage { QuickNotesSettings appSettings = (Application.Current as App).AppSettings; public MainPage() { InitializeComponent(); txtbox.Text = appSettings.Text; txtbox.FontSize = appSettings.FontSize; } void OnTextBoxTextChanged(object sender, TextChangedEventArgs args) { appSettings.Text = txtbox.Text; } void OnAppBarSmallerFontClick(object sender, EventArgs args) { txtbox.FontSize = Math.Max(12, txtbox.FontSize - 1); appSettings.FontSize = txtbox.FontSize; } void OnAppBarLargerFontClick(object sender, EventArgs args) { txtbox.FontSize = Math.Min(48, txtbox.FontSize + 2); appSettings.FontSize = txtbox.FontSize; } } Whenever the text in the TextBox changes, the OnTextBoxChanged method saves the new version in application settings. The two methods to increase and decrease the font size similarly save the new setting but also use it to set the FontSize property of the TextBox. Here’s the program in action: 293 What the program does not do is save the text insertion point (visually indicated by the TextBox caret), so whenever the program starts up, you need to tap on the screen to indicate where you want to continue typing. It’s possible that you left off at the end of the file, but QuickNotes will always bring you back to the top. I toyed around with fixing this problem. The insertion point is available as the SelectionStart, property, and as the name suggests, it’s used in conjunction with text selection. There’s also a SelectionLength property, which has a value of 0 if no text is selected. (You can also access or set the selected text using the SelectedText property.) TextBox also has a SelectionChanged event, so it’s certainly possible for QuickNotes to save the new value of SelectionStart in application settings every time it changes. Then it would be a simple matter to set the SelectionStart property along with Text and FontSize in the constructor of MainPage. But that doesn’t quite work. When you launch QuickNotes or return to it after navigating away, the TextBox doesn’t have input focus. You need to tap on the screen to give the TextBox focus and start typing something in. But by tapping on the screen, you’re also setting a new insertion point! The solution to that little problem is to give input focus to the TextBox programmatically. It doesn’t work in the constructor to MainPage, but if you install a handler for the Loaded event, you can do it there: txtbox.Focus(); 294 But doing that creates quite a dramatic entrance to the program! As soon as the program starts up, the virtual keyboard pops up! I struggled with the propriety of doing that, and at last I decided it was just too intrusive. But who knows? Maybe I’ll put that feature back in at a later time. That’s why they call it software. 295 Chapter 11 Dependency Properties This chapter is about creating custom control classes in Silverlight, optionally making them available in dynamic link libraries, and referencing them in code and markup. Deriving one class from another is such a basic aspect of object-oriented programming that devoting a whole chapter to the topic hardly seems necessary. And in one sense, you don’t need to do anything special to derive a custom class from an existing Silverlight class. You can reference that class in XAML just by providing an XML namespace declaration to associate an XML prefix with your .NET namespace. I demonstrated as much in Chapter 9 in the two projects that showed how to create custom panels. On the other hand, if you’re creating a custom control class, and that class defines new properties, and if you want those properties to be set through styles, or you want those properties to be set through data bindings, or you want those properties to be the target of animations, then you need to do something very special with those properties. You need to make them dependency properties, The Problem Illustrated To illustrate the difference that dependency properties make, let’s first look at a custom control class perhaps coded by a naïve programmer Suppose you want to use a bunch of buttons whose foregrounds are colored with various linear gradient brushes, and you figure it would be convenient for you to specify the two colors as properties of the buttons, perhaps properties named Color1 and Color2. So you open a project named NaiveGradientButtonDemo and add a new class named NaiveGradientButton. Here’s that class: Silverlight Project: NaiveGradientButtonDemo File: NaiveGradientButton.cs (excerpt) public class NaiveGradientButton : Button { GradientStop gradientStop1, gradientStop2; public NaiveGradientButton() { LinearGradientBrush brush = new LinearGradientBrush(); brush.StartPoint = new Point(0, 0); brush.EndPoint = new Point(1, 0); 296 gradientStop1 = new GradientStop(); gradientStop1.Offset = 0; brush.GradientStops.Add(gradientStop1); gradientStop2 = new GradientStop(); gradientStop2.Offset = 1; brush.GradientStops.Add(gradientStop2); Foreground = brush; } public Color Color1 { set { gradientStop1.Color = value; } get { return (Color)gradientStop1.Color; } } public Color Color2 { set { gradientStop2.Color = value; } get { return (Color)gradientStop2.Color; } } } As expected, NaiveGradientButton derives from Button and has two new properties of type Color named Color1 and Color2. The constructor creates a LinearGradientBrush, sets the StartPoint and EndPoint properties, creates two GradientStop objects that are stored as fields, adds those to the LinearGradientBrush, and then sets the brush to the button’s Foreground property. This class will not prevent the Foreground property of the GradientBrush from being re-set in code or XAML after the object has been created, but because the code that sets the Foreground here is considered to be a local setting, it will prevent inheritance of the Foreground property, and won’t be affected by a Style that targets the Foreground property. As you can see, the set and get accessors of the Color1 and Color2 properties are implemented simply to access the Color property in the corresponding GradientStop. The MainPage.xaml file in the NaiveGradientButtonDemo project references this class. The root element includes an XML namespace declaration that associates the namespace prefix “local” with the CLR namespace of NaiveGradientButton: xmlns:local="clr-namespace:NaiveGradientButtonDemo" The Resources collection in MainPage.xaml defines a Style for NaiveGradientButton: 297 Silverlight Project: NaiveGradientButtonDemo File: MainPage.xaml (excerpt) <phone:PhoneApplicationPage.Resources> <Style x:Key="gradientButtonStyle" TargetType="local:NaiveGradientButton"> <Setter Property="HorizontalAlignment" Value="Center" /> <! <Setter Property="Color1" Value="Cyan" /> <Setter Property="Color2" Value="Pink" /> > </Style> </phone:PhoneApplicationPage.Resources> Notice the style TargetType referencing the custom class by prefacing the class name with the XML namespace. You’ll also notice that I’ve commented out Setter tags that target the Color1 and Color2 properties. (Perhaps I’m not as naïve as I sometimes pretend to be.) The content area of the XAML file has four instances of NaiveGradientButton with their Color1 and Color2 properties set in a variety of different ways: Silverlight Project: NaiveGradientButtonDemo File: MainPage.xaml (excerpt) <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel> <local:NaiveGradientButton Content="Naive Gradient Button #1" HorizontalAlignment="Center" /> <local:NaiveGradientButton Content="Naive Gradient Button #2" Color1="Blue" Color2="Red" HorizontalAlignment="Center" /> <local:NaiveGradientButton Content="Naive Gradient Button #3" Color1="{StaticResource PhoneForegroundColor}" Color2="{StaticResource PhoneBackgroundColor}" HorizontalAlignment="Center" /> <local:NaiveGradientButton Content="Naive Gradient Button #4" Style="{StaticResource gradientButtonStyle}" /> </StackPanel> </Grid> 298 The first button uses the default values of Color1 and Color2; the second uses explicit colors; the third references theme colors, and the fourth references the Style defined in the Resources collection. When you run the program, you’ll discover that the second and third buttons are fine, but the first and fourth seem to have no content: There are no default values for Color1 and Color2. If they’re not explicitly set, the colors in the gradient will have all the A, R, G, and B properties set to 0, a color known as transparent black. Try uncommenting the two Setter tags in the Style. The Visual Studio error window will tell you “Object reference not set to an instance of an object” (certainly one of my favorite error messages) and if you try to run the program under the debugger, a XamlParseException will be raised with the message “Invalid attribute value Color1 for property Property.” That’s a little better: It’s telling you that in the Setter tag, you can’t set Property to Color1. What the error message should really say is: “Don’t be so naïve. Use dependency properties.” The Dependency Property Difference In Silverlight, properties can be set in several ways. We have empirically discovered that a strict precedence is established when the same property is set from property inheritance, or from a theme, or a style, or a local setting. A little chart created in Chapter 7 reads: 299 Local Settings have precedence over Style Settings, which have precedence over the Theme Style, which has precedence over Property Inheritance, which has precedence over Default Values In chapters ahead you’ll see that properties can be set from animations and templates, and these also fit into the precedence chart. This strict precedence is required to avoid a lot of fighting and squabbles among styles and animations and everything else. It would be chaos otherwise, and that violates our fundamental desire that code be completely deterministic. What Silverlight providesis an infrastructure to manage all the different ways properties can be set and to impose some kind of order. Dependency properties are a major part of this infrastructure. They’re called dependency properties because the properties depend on a bunch of different external forces, which are then mediated. Dependency properties are built on top of existing .NET properties, and there’s some grunt work involved, and some extra typing, but you’ll be coding dependency properties automatically before you know it. Among other things, dependency properties provide the property-setting precedence. It occurs way under the covers and it’s not something you can mess with. Dependency properties also provide a very structured way to give properties a default value, and to provide callback methods that are invoked when the value of the property changes. Almost all the properties of the Silverlight classes encountered so far have actually been dependency properties. It’s easier listing the exceptions to this rule! Two that come to mind are the Children property of Panel and the Text property of Run. Any class that implements dependency properties must derive from DependencyObject, which is a very basic class in the Silverlight class hierarchy. Many classes in Silverlight derive from DependencyObject, including the big one: UIElement. That means Button derives from DependencyObject, which of course means that any class that derives from Button can implement dependency properties. A BetterGradientButton class with dependency properties starts off normally: public class BetterGradientButton : Button { } 300 [...]... RgbColorScroller ColorColumn and RgbColorScroller can both be found in a dynamic link library (DLL) project called Petzold .Phone. Silverlight Creating a DLL in Visual Studio for your Windows Phone programs is easy: In the New Project dialog, select Silverlight for Windows Phone at the left and Windows Phone Class Library in the middle area (To facilitate testing, you’ll probably want a second application project... BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="2" Margin= "4 2" Padding= "4" > 3 07 The really great news is in the screen shot: The first button shows the... SilverlightProject: BetterGradientButtonDemo File: MainPage.xaml (excerpt) < /phone: PhoneApplicationPage.Resources> The content area is basically the... here’s the complete ColorColumn.xaml file Notice I’ve also changed the Background property on the Grid from a StaticResource referencing PhoneChromeBrush to Transparent: 311 Silverlight Project: Petzold .Phone. Silverlight File: ColorColumn.xaml ... controls Here’s the complete XAML file: Silverlight Project: Petzold .Phone. Silverlight File: RgbColorScroller.xaml ... namespace declaration for the library Because the library is a separate assembly, this namespace declaration requires an assembly section to refer to the DLL file: 3 17 xmlns:petzold="clr-namespace:Petzold .Phone. Silverlight;assembly=Petzold .Phone. Silverlight" The SelectTwoColors XAML file has two RgbColorScroller controls, each inside a Border with a Rectangle element between them Each RgbColorScroll has... . assured that they’ll be retained without any explicit saving or loading. It’s basically a Windows Phone 7 version of Notepad but only capable of working with a single file. The program also allows. NaiveGradientButton: 2 97 Silverlight Project: NaiveGradientButtonDemo File: MainPage.xaml (excerpt) < ;phone: PhoneApplicationPage.Resources>. font" Click="OnAppBarLargerFontClick" /> </shell:ApplicationBar> < /phone: PhoneApplicationPage.ApplicationBar> With all this preparation, the actual code file for