1. Trang chủ
  2. » Công Nghệ Thông Tin

programming windows phone 7 phần 7 potx

102 305 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 102
Dung lượng 1,65 MB

Nội dung

</Button.Style> </Button> Here’s what we’re up to so far: The button still displays the text “temporary” and it should really be displaying the text “Click me!” You might be tempted to put a TextBlock in there and set its Text property to a TemplateBinding of the Content property of the Button: <TextBlock Text="{TemplateBinding Content}" /> This actually works in this example, but it’s very, very wrong. The problem is that the Content property of the Button is of type object. We can set it to anything—an Image, a Panel, a Shape, a RadialGradientBrush, and then the TextBlock would have a little problem. Fortunately, there is a class in Silverlight that exists specifically to display content in a ContentControl derivative. That class is called ContentPresenter. It has a property named Content of type object, and ContentPresenter displays that object regardless whether it’s a text string or any other element: <Button Content="Click me!" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button.Style> <Style TargetType="Button"> <Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" /> <Setter Property="BorderThickness" Value="6" /> <Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" /> 597 <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="12"> <ContentPresenter Content="{TemplateBinding Content}" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Button.Style> </Button> Notice how the Content property of the ContentPresenter is bound to the Content property of the Button. The ContentPresenter has the distinct advantage of working for any kind of object. The ContentPresenter might create its own visual tree; for example, if the Content is of type string, then the ContentPresenter creates a TextBlock to display that string. The ContentPresenter is also entrusted with the job of building a visual tree to display content based on a DataTemplate set to the Control. For this purpose, the ContentPresenter has its own ContentTemplate property that you can bind to the ContentTemplate of the control: <Button Content="Click me!" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button.Style> <Style TargetType="Button"> <Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" /> <Setter Property="BorderThickness" Value="6" /> <Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="12"> <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </Button.Style> </Button> 598 These two TemplateBinding settings on ContentPresenter are so standard that they are not required to be explicitly set! They will be set for you. I feel more comfortable seeing them explicitly set, however. You may recall that the Control class defines a property named Padding that is intended to provide a little breathing room around the control’s content. Try setting the Padding property of the Button: <Button Content="Click me!" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="24"> … </Button> Nothing happens. The visual tree in the template needs to accommodate this Padding property. It needs to leave some space between the Border and the ContentPresenter. How can this be done? One solution is to use a TemplateBinding on the Padding property of the Border. But if there’s some other stuff in the Border besides the ContentPresenter that’s not going to work right. The standard approach is to set a TemplateBinding on the Margin property of the ContentPresenter: <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" /> You don’t need to set a Padding value on the Button for this to have an effect. The theme Style for the Button defines a Padding value that seems to work well with this Button, even with the rounded Border corners. Now try setting the HorizontalAlignment and VerticalAlignment properties of the Button to Stretch. These work fine, so that’s something you don’t have to worry about in the template. Similarly, you can set the Margin property of the Button and that’s still recognized by the layout system. But when you set the HorizontalAlignment and VerticalAlignment properties of the Button to Stretch, you’ll discover that the content of the Button is at the upper-left corner: 599 Control defines two properties named HorizontalContentAlignment and VerticalContentAlignment that are supposed to govern how the content is aligned within the ContentControl. If you set these properties on the button, you’ll discover that they don’t work. This tells us that we need to add something to the template to handle these properties. We have to align the ContentPresenter within the Border based on the HorizontalContentAlignment and VerticalContentAlignment properties. This is accomplished by providing TemplateBinding markup targeting the HorizontalAlignment and VerticalAlignment properties of the ContentPresenter: <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> Again, this is very standard markup for a ContentPresenter. It’s copy-and-paste stuff. If you set the font-related properties or the Foreground property on the Button, you’ll find that the text changes accordingly. These properties are inherited through the visual tree of the template and you don’t need to do anything in the template to accommodate them. (However the theme Style for the Button explicitly sets the Foreground, FontFamily, and FontSize properties, so the Button itself cannot inherit these properties through the visual tree, and there is apparently nothing you can do in a custom Style to change this behavior.) 600 The Visual State Manager All this time that the Button has been redesigned with a template, it has otherwise remained a fully-functional button and it’s been generating Click events every time it’s been tapped. The big problem is that the Button does not deliver visual feedback to the user. It has a customized visual appearance, but that appearance does not change. There are really just two features that need to be added to this template to make it functionally and visually complete: • The Button needs to provide visual feedback when the user presses it. • The Button needs to indicate a disabled state if it’s disabled. These two features are related because they both involve changing the visuals of the control under certain circumstances. And the two features are also related because the solution involves a Silverlight feature called the Visual State Manager. The Visual State Manager helps the developer deal with visual states, which are changes in control visuals that result from changes in properties (or other states) of the control. For the Button on Windows Phone 7, the relevant visual states correspond to the properties IsPressed and IsEnabled. You can determine the visual states supported by a particular control by looking at the documentation for that control. In the first page of the Button documentation, you’ll see the class defined with six attributes of type TemplateVisualStateAttribute: [TemplateVisualStateAttribute(Name = "Disabled", GroupName = "CommonStates")] [TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")] [TemplateVisualStateAttribute(Name = "MouseOver", GroupName = "CommonStates")] [TemplateVisualStateAttribute(Name = "Pressed", GroupName = "CommonStates")] [TemplateVisualStateAttribute(Name = "Unfocused", GroupName = "FocusStates")] [TemplateVisualStateAttribute(Name = "Focused", GroupName = "FocusStates")] public class Button : ButtonBase These are the six visual states of the Button. Each of these states has a name, but notice also that each of them has a group name, either CommonStates or FocusStates. Within any group, the visual states are mutually exclusive. One and only one visual state in each group currently applies to the Button. In the CommonStates group, either a button is Normal, or Disabled, or the mouse is hovering, or the button is pressed. You don’t need to worry about combinations of these states. You don’t have to come up with a special state for mouse hovering on a disabled button because those two states will never occur at the same time. 601 The code in the Button class is responsible for the button going into a particular state. It does this through calls to the static VisualStateManager.GoToState method. The template is responsible for responding to visual changes based on these states. As Windows Phone 7 programmers, we have a somewhat easier job than template authors targeting Silverlight on the web. We don’t have to worry about the two states in the FocusStates group, or the MouseOver state. That leaves Normal, Disabled, and Pressed. Very often, additional elements are inserted into the template specifically for these visual states. When a control is disabled, the contents usually get grayer in some way regardless of the nature of the content—be it text, a bitmap, or something else. This suggests that a disabled state can be handled by putting a semi-opaque Rectangle on top of the entire control. So let’s put the entire visual tree of the template inside a single-cell Grid and add a Rectangle on top: <ControlTemplate TargetType="Button"> <Grid> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="24"> <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> </Border> <Rectangle Name="disableRect" Fill="{StaticResource PhoneBackgroundBrush}" Opacity="0" /> </Grid> </ControlTemplate> Fortunately the new Rectangle has its Opacity set to 0 or it would block out the entire control! But if you set the Opacity to 0.6 (for example) it provides a proper dimming effect, regardless of control content. Notice the color of the Rectangle is set as the PhoneBackgroundBrush resource. The Button has rounded corners so you don’t want the Rectangle changing the color of whatever’s behind the Button and visible through those corners. It’s also possible to give the Rectangle the same corner rounding as the Border and you’ll have a little more flexibility with the color. Now that we have the Rectangle in there, all we need to do is find some way to change the Opacity from 0 to 0.6 when the visual state becomes Disabled. 602 The markup for the Visual State Manager always appears after the start tag of the top level element of the template, in this case the Grid. It begins with a VisualStateManager.VisualStateGroups tag within which can be multiple VisualStateGroups sections. I’ll be ignoring the FocusStates group: <ControlTemplate TargetType="Button"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> </VisualStateGroup> </VisualStateManager.VisualStateGroups> … </Grid> </ControlTemplate> The VisualStateGroup tags enclose a series of VisualState tags for each of the visual states in that group: <ControlTemplate TargetType="Button"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="MouseOver" /> <VisualState x:Name="Pressed"> </VisualState> <VisualState x:Name="Disabled"> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> … </Grid> </ControlTemplate> The VisualState tag for the Normal state is empty because the template that’s already been designed is for the normal button. But don’t leave out the tag; otherwise the control won’t return to its Normal state after being in another state. The MouseOver state won’t be used; it too will remain empty. Within the VisualState tags you indicate what you want to happen for that state. How do you do that? You might imagine doing it with a Setting tag as in a Style, and that would work well. But to allow you to be very much more flexible, the Visual State Manager lets you use animations instead, and because the animation syntax isn’t very much more complex than the Setting syntax, the Visual State Manager actually requires you to use animations. Within the VisualState tags you’ll put a Storyboard containing one or more animations targeting properties of named elements within the template. In many cases, these animations will have 603 a Duration setting of 0 so that the visual state changes immediately. But you can have smoother state animations if you want. Here’s an animation for the Opacity property of the Rectangle named disableRect: <ControlTemplate TargetType="Button"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="MouseOver" /> <VisualState x:Name="Pressed"> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <DoubleAnimation Storyboard.TargetName="disableRect" Storyboard.TargetProperty="Opacity" To="0.6" Duration="0:0:0" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> … </Grid> </ControlTemplate> The animations in the Visual State Manager don’t usually have From values so they just take off from the existing value. The empty VisualState tag for the Normal state effectively restores the Opacity value back to its pre-animation value when the state goes back to Normal. The Pressed state presents some challenges. Usually a Pressed state is often rendered as a form of reverse video. In the web version of Silverlight, the Button template hard-codes a LinearGradientBrush for the background and changes properties of that brush for the Pressed state. Because the template is controlling the brush for the Normal state, it can easily change that brush for the Pressed state. In the Button template being created here, the default Foreground color is set in the theme style for the Button, and a default Background color is set in the Style that the template is part of. If these properties aren’t changed, the colors will be either white on black (with the “dark” theme) or black on white. But the properties can be changed with local property settings on the Button. It would be great to have some kind of graphic effect to reverse the colors, but that’s not available. We need to define animations to set new foreground and background colors for the Pressed state that will seem to reverse colors in the normal case, that is, when the foreground is set to the PhoneForegroundBrush resource and the background is PhoneBackgroundBrush, 604 which means that the Pressed state can set Foreground to PhoneBackgroundBrush and Background to PhoneForegroundBrush. Can we use a ColorAnimation for this job? We could if we knew that the Foreground and Background brushes were actually SolidColorBrush objects. But we don’t know that. That means we need to use ObjectAnimationUsingKeyFrames objects to target the Foreground and Background properties themselves. The ObjectAnimationUsingKeyFrames can have children of only one type: DiscreteObjectKeyFrame. Let’s do the Background property first by giving the Border a name: <Border Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="12"> That name allows the animation to target the Background property of Border: <VisualState x:Name="Pressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{StaticResource PhoneForegroundBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> For the Pressed state, the animation changes the Background property of the Border element to the brush referenced as the PhoneForegroundBrush resource. Excellent! Now let’s add a similar animation to target the Foreground property of … of what? There is no element in this template visual tree that has a Foreground property! It would be ideal if ContentPresenter had a Foreground property but it does not. But wait a minute. What about ContentControl? ContentControl is basically just a ContentPresenter but ContentControl has a Foreground property. So let’s replace ContentPresenter with a ControlControl and give it a name: <ContentControl Name="contentControl" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> Now it’s possible to define the second animation for the Pressed state: <VisualState x:Name="Pressed"> <Storyboard> 605 … <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentControl" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{StaticResource PhoneBackgroundBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> And here’s the pressed Button: I will now declare this template to be completed! (And now you’ll understand why the default template for the Button contains a ContentControl.) Let’s look at this entire Style and ControlTemplate in the context of a page. In the CustomButtonTemplate program, the Style is defined in the page’s Resources collection. Mostly to reduce keep the lines lengths shorter than the width of the page, the ControlTemplate is defined as a separate resource and then referenced by the Style. Here’s the ControlTemplate first followed by the Style referencing that template: Silverlight Project: CustomButtonTemplate File: MainPage.xaml (excerpt) <phone:PhoneApplicationPage.Resources> <ControlTemplate x:Key="buttonTemplate" TargetType="Button"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> 606 [...]... Fill="{StaticResource PhoneBackgroundBrush}" Opacity="0" /> 6 07 < /phone: PhoneApplicationPage.Resources>... CheckedContent is displayed for the Checked state I defined this class in the Petzold .Phone. Silverlight library The complete code for FadableToggleButton is here: Silverlight Project: Petzold .Phone. Silverlight File: FadableToggleButton.cs using System .Windows; using System .Windows. Controls.Primitives; namespace Petzold .Phone. Silverlight { public class FadableToggleButton : ToggleButton { public static... collection of the page: Silverlight Project: AltSliderDemo File: MainPage.xaml (excerpt) < /phone: PhoneApplicationPage.Resources> The only purpose of this Style is to demonstrate that... ControlTemplate to Petzold .Phone. Silverlight as the default Style for a class named AltSlider The class has no additional properties so the code file needs only identify the class that AltSlider derives from and what class should be used for locating the default Style: Silverlight Project: Petzold .Phone. Silverlight File: AltSlider.cs using System .Windows. Controls; namespace Petzold .Phone. Silverlight { public... AlternativeSlider project: 6 27 Silverlight Project: Petzold .Phone. Silverlight File: Themes/generic.xaml (excerpt) … ... ... complex templates in all of standard Silverlight, and for that reason, it’s important to get familiar with it—particularly if you’re not a big fan of the default Slider template implemented in Windows Phone 7 At first, a Slider does not seem to fit into the scheme of templates, primarily because it contains moving parts How does this work exactly? If you look at the documentation of Slider, you’ll... specifically for that purpose The Thumb is a rather special control that reports how the user is trying to drag it But if you can’t quite figure out where the Thumb is located in the standard Slider on Windows Phone 7, that’s because it’s been pretty well hidden in the theme template One of my goals here is to restore the Thumb to the Slider A control with parts (such as the Slider) overrides the ApplyTemplate . that result from changes in properties (or other states) of the control. For the Button on Windows Phone 7, the relevant visual states correspond to the properties IsPressed and IsEnabled. You. The template is responsible for responding to visual changes based on these states. As Windows Phone 7 programmers, we have a somewhat easier job than template authors targeting Silverlight. Value="{StaticResource PhoneChromeBrush}" /> <Setter Property="Template" Value="{StaticResource buttonTemplate}" /> </Style> < /phone: PhoneApplicationPage.Resources>

Ngày đăng: 13/08/2014, 08:20

TỪ KHÓA LIÊN QUAN

w