programming windows phone 7 phần 6 pdf

102 241 0
programming windows phone 7 phần 6 pdf

Đ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

appbarSaveButton.IsEnabled = true; } } The “monochromized” WriteableBitmap is set to the Source property of the Image element and the save button is enabled. Pressing the save button navigates to the SaveFileDialog.xaml page in the Petzold.Phone.Silverlight library. As you’ve just seen, the SaveFileDialog class handles its OnNavigatedFrom override by calling the SaveFileDialogCompleted method in the class that it’s navigating to: Silverlight Project: Monochromize File: MainPage.xaml.cs (excerpt) void OnAppbarSaveClick(object sender, EventArgs args) { this.NavigationService.Navigate( new Uri("/Petzold.Phone.Silverlight;component/SaveFileDialog.xaml", UriKind.Relative)); } public void SaveFileDialogCompleted(bool okPressed, string filename) { if (okPressed) { MemoryStream memoryStream = new MemoryStream(); 495 writeableBitmap.SaveJpeg(memoryStream, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight, 0, 75); memoryStream.Position = 0; MediaLibrary mediaLib = new MediaLibrary(); mediaLib.SavePicture(filename, memoryStream); } } The SaveFileDialogCompleted method uses the filename entered by the user to write the bitmap to the pictures library. This happens in two steps: First the SaveJpeg method writes the WriteableBitmap to a MemoryStream in JPEG format. The Position on the MemoryStream is then reset, and the stream is saved to the pictures library. The Monochromize program also handles tombstoning. The OnNavigatedFrom method uses the SaveJpeg extension method to write to a MemoryStream and then saves the byte array. This method is also responsible for calling SetTitle on the SaveFileDialog if navigating to that page: Silverlight Project: Monochromize File: MainPage.xaml.cs (excerpt) protected override void OnNavigatedFrom(NavigationEventArgs args) { if (writeableBitmap != null) { MemoryStream stream = new MemoryStream(); writeableBitmap.SaveJpeg(stream, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight, 0, 75); appService.State["jpegBits"] = stream.GetBuffer(); } if (args.Content is SaveFileDialog) { SaveFileDialog page = args.Content as SaveFileDialog; page.SetTitle(ApplicationTitle.Text); } base.OnNavigatedFrom(args); } The OnNavigatedTo method is responsible for re-activating after tombstoning. The byte array is converted by to a WriteableBitmap, and the save button is enabled: Silverlight Project: Monochromize File: MainPage.xaml.cs (excerpt) protected override void OnNavigatedTo(NavigationEventArgs args) { if (appService.State.ContainsKey("jpegBits")) 496 { byte[] bitmapBits = (byte[])appService.State["jpegBits"]; MemoryStream stream = new MemoryStream(bitmapBits); BitmapImage bitmapImage = new BitmapImage(); bitmapImage.SetSource(stream); writeableBitmap = new WriteableBitmap(bitmapImage); img.Source = writeableBitmap; appbarSaveButton.IsEnabled = true; } base.OnNavigatedTo(args); } Becoming a Photo Extras Application Architecturally and functionally, the Posterizer program that concludes this chapter is similar to the Monochromize program. It lets the user select a photo from the picture library and to save it back in the Saved Pictures album. But the Posterizer program allows the user to reduce the bit resolution of each color independently (creating a poster-like effect) and for this it needs to display a row of RadioButton elements. The program must also retain the original unadulterated pixels array so it can restore the image to full color resolution. In addition, Posterizer registers itself as a “photos extra” application, which means that it can be invoked by the user from the picture library itself. For maximum convenience, I decided to implement the controls to select the bit resolution as an overlay: 497 The middle ApplicationBar button toggles the visibility of that overlay. The accent color is used to indicate the selected value in each column. This overlay is a UserControl derivative called BitSelectDialog, and I’ll discuss that control first. The visual tree just defines a Grid with three columns and nine rows: Silverlight Project: Posterizer File: BitSelectDialog.xaml (excerpt) <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> </Grid> Each cell is a TextBlock and the code-behind file handles the manipulation logic to make them behave like radio buttons. The public interface to the class includes an event and a public property that stores the three current settings: Silverlight Project: Posterizer File: BitSelectDialog.xaml.cs (excerpt) public event EventHandler ColorBitsChanged; … public int[] ColorBits { protected set; get; } You may see a little flaw in this already. Although the set accessor for the ColorBits array is protected and the array cannot be replaced by an external class, the individual members of the array can be set, and there is no way for the class to know about it, let alone to fire the ColorBitsChanged event. But I allowed the flaw to exist rather than make the class more complex. The class creates all the TextBlock elements in the constructor. Notice that the ColorBits array is initialized to contain three values of 2. 498 Silverlight Project: Posterizer File: BitSelectDialog.xaml.cs (excerpt) public partial class BitSelectDialog : UserControl { Brush selectedBrush; Brush normalBrush; TextBlock[,] txtblks = new TextBlock[3, 9]; … public BitSelectDialog() { InitializeComponent(); ColorBits = new int[3]; ColorBits[0] = 2; ColorBits[1] = 2; ColorBits[2] = 2; selectedBrush = this.Resources["PhoneAccentBrush"] as Brush; normalBrush = this.Resources["PhoneForegroundBrush"] as Brush; string[] colors = { "red", "green", "blue" }; for (int col = 0; col < 3; col++) { TextBlock txtblk = new TextBlock { Text = colors[col], FontWeight = FontWeights.Bold, TextAlignment = TextAlignment.Center, Margin = new Thickness(8, 2, 8, 2) }; Grid.SetRow(txtblk, 0); Grid.SetColumn(txtblk, col); LayoutRoot.Children.Add(txtblk); for (int bit = 0; bit < 9; bit++) { txtblk = new TextBlock { Text = bit.ToString(), Foreground = bit == ColorBits[col] ? selectedBrush : normalBrush, TextAlignment = TextAlignment.Center, Padding = new Thickness(2), Tag = col.ToString() + bit }; Grid.SetRow(txtblk, bit + 1); Grid.SetColumn(txtblk, col); LayoutRoot.Children.Add(txtblk); txtblks[col, bit] = txtblk; 499 } } } } Each TextBlock has a two-character string set to its Tag property to indicate the color and the number of bits associated with that element. I also defined a public method that allows a program to initialize the three ColorBits values, changing the TextBlock colors in the process but not raising the ColorBitsChanged event. This was useful during re-activating from a tombstoned condition. Silverlight Project: Posterizer File: BitSelectDialog.xaml.cs (excerpt) public void Initialize(int[] colorBits) { for (int clr = 0; clr < 3; clr++) { txtblks[clr, ColorBits[clr]].Foreground = normalBrush; ColorBits[clr] = colorBits[clr]; txtblks[clr, ColorBits[clr]].Foreground = selectedBrush; } } The OnManipulationStarted override decodes the Tag property from the touched TextBlock to determine the user’s selection: Silverlight Project: Posterizer File: BitSelectDialog.xaml.cs (excerpt) protected override void OnManipulationStarted(ManipulationStartedEventArgs args) { if (args.OriginalSource is TextBlock) { TextBlock txtblk = args.OriginalSource as TextBlock; string tag = txtblk.Tag as string; if (tag != null && tag.Length == 2) { int clr = Int32.Parse(tag[0].ToString()); int bits = Int32.Parse(tag[1].ToString()); if (ColorBits[clr] != bits) { txtblks[clr, ColorBits[clr]].Foreground = normalBrush; ColorBits[clr] = bits; txtblks[clr, ColorBits[clr]].Foreground = selectedBrush; if (ColorBitsChanged != null) ColorBitsChanged(this, EventArgs.Empty); 500 } args.Complete(); args.Handled = true; } } base.OnManipulationStarted(args); } Based on the information decoded from the Tag property of the touched TextBlock, the method can recolor that TextBlock (and the one becoming unselected), store a new value in the ColorBits array, and raise the ColorBitsChanged event. In the MainPage class of the program itself, the content area includes a (by now familiar) Image element with no bitmap and this BitSelectDialog control: Silverlight Project: Posterizer File: MainPage.xaml (excerpt) <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Image Name="img" /> <local:BitSelectDialog x:Name="bitSelectDialog" Visibility="Collapsed" FontSize="{StaticResource PhoneFontSizeExtraLarge}" HorizontalAlignment="Center" VerticalAlignment="Center" ColorBitsChanged="OnBitSelectDialogColorBitsChanged" /> </Grid> Notice that the BitSelectDialog control has its Visibility property set to Collapsed. The XAML file also contains three buttons in its ApplicationBar: Silverlight Project: Posterizer File: MainPage.xaml (excerpt) <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar> <shell:ApplicationBarIconButton x:Name="appbarLoadButton" IconUri="/Images/appbar.folder.rest.png" Text="load" Click="OnAppbarLoadClick" /> <shell:ApplicationBarIconButton x:Name="appbarSetBitsButton" IconUri="/Images/appbar.feature.settings.rest.png" Text="set bits" IsEnabled="False" Click="OnAppbarSetBitsClick" /> 501 <shell:ApplicationBarIconButton x:Name="appbarSaveButton" IconUri="/Images/appbar.save.rest.png" Text="save" IsEnabled="False" Click="OnAppbarSaveClick" /> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar> In the code-behind file, the fields of the MainPage class include three variables that represent bitmaps: of type WriteableBitmap, byte array, and int array. Silverlight Project: Posterizer File: MainPage.xaml.cs (excerpt) public partial class MainPage : PhoneApplicationPage, ISaveFileDialogCompleted { PhoneApplicationService appService = PhoneApplicationService.Current; PhotoChooserTask photoChooser = new PhotoChooserTask(); WriteableBitmap writeableBitmap; byte[] jpegBits; int[] pixels; public MainPage() { InitializeComponent(); appbarLoadButton = this.ApplicationBar.Buttons[0] as ApplicationBarIconButton; appbarSetBitsButton = this.ApplicationBar.Buttons[1] as ApplicationBarIconButton; appbarSaveButton = this.ApplicationBar.Buttons[2] as ApplicationBarIconButton; photoChooser.Completed += OnPhotoChooserCompleted; } } The WriteableBitmap field is the bitmap that is set to the Image element and displayed to the user. This is the bitmap that has its pixels adjusted for lower color resolution. The jpegBits array is the original file that the user loads from the picture library. Retaining the jpegBits array is very convenient for tombstoning, and ensures that the photo reconstituted after tombstoning is the same one that was originally loaded. The pixels array stores the unaltered pixels of the loaded bitmap, but this is not saved during tombstoning. Saving jpegBits rather than pixels requires much less storage. When the user presses the “load” button, the PhotoChooserTask is invoked. During the Completed event, the program sets jpegBits from the ChosenPhoto stream, and then calls LoadBitmap. 502 Silverlight Project: Posterizer File: MainPage.xaml.cs (excerpt) void OnAppbarLoadClick(object sender, EventArgs args) { bitSelectDialog.Visibility = Visibility.Collapsed; appbarSetBitsButton.IsEnabled = false; appbarSaveButton.IsEnabled = false; photoChooser.Show(); } void OnPhotoChooserCompleted(object sender, PhotoResult args) { if (args.Error == null && args.ChosenPhoto != null) { jpegBits = new byte[args.ChosenPhoto.Length]; args.ChosenPhoto.Read(jpegBits, 0, jpegBits.Length); LoadBitmap(jpegBits); } } void LoadBitmap(byte[] jpegBits) { // Create WriteableBitmap from JPEG bits MemoryStream memoryStream = new MemoryStream(jpegBits); BitmapImage bitmapImage = new BitmapImage(); bitmapImage.SetSource(memoryStream); writeableBitmap = new WriteableBitmap(bitmapImage); img.Source = writeableBitmap; // Copy pixels into field array pixels = new int[writeableBitmap.PixelWidth * writeableBitmap.PixelHeight]; for (int i = 0; i < pixels.Length; i++) pixels[i] = writeableBitmap.Pixels[i]; appbarSetBitsButton.IsEnabled = true; appbarSaveButton.IsEnabled = true; ApplyBitSettingsToBitmap(); } The LoadBitmap method then turns that byte array back into a MemoryStream for creating the BitmapImage and WriteableBitmap. This may seem like a roundabout way to create the bitmap, but it makes much more sense in conjunction with tombstoning. The LoadBitmap method then makes a copy of the Pixels array of the WriteableBitmap as the pixels field. This pixels field will remain unaltered as the Pixels array of the WriteableBitmap is changed based on the user selection of bit resolution. The objective is to do nothing irrevocable. The user should always be able to select a greater color resolution after choosing a lower one. 503 The ApplyBitSettingsToBitmap called at the end of the LoadBitmap method is also called whenever the ColorBitsChanged event is fired by the BitSelectDialog. The visibility of that dialog is toggled on and off by the middle button in the ApplicationBar: Silverlight Project: Posterizer File: MainPage.xaml.cs (excerpt) void OnAppbarSetBitsClick(object sender, EventArgs args) { bitSelectDialog.Visibility = bitSelectDialog.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed; } void OnBitSelectDialogColorBitsChanged(object sender, EventArgs args) { ApplyBitSettingsToBitmap(); } void ApplyBitSettingsToBitmap() { if (pixels == null || writeableBitmap == null) return; int mask = -16777216; // ie, FF000000 for (int clr = 0; clr < 3; clr++) mask |= (byte)(0xFF << (8 - bitSelectDialog.ColorBits[clr])) << (16 - 8 * clr); for (int i = 0; i < pixels.Length; i++) writeableBitmap.Pixels[i] = mask & pixels[i]; writeableBitmap.Invalidate(); } The mask variable is built from the three bit-resolution values, and then applied to all the values in the pixels field to set all the values in the Pixels array of the WriteableBitmap. The Posterizer program also does something a little special when the user presses the button to save the file to the picture library. The program wants to suggest to the user a filename of Posterizer followed by a three digit number higher than anything currently in the picture library. It obtains the saved pictures album through the SavedPictures property of the MediaLibrary and searches for matching filenames: Silverlight Project: Posterizer File: MainPage.xaml.cs (excerpt) void OnAppbarSaveClick(object sender, EventArgs args) { int fileNameNumber = 0; 504 [...]... rotate2.Angle = (elapsedTime.TotalMinutes * 360 ) % 360 ; } void OnButtonClick(object sender, RoutedEventArgs args) { Thread.Sleep(5000); } } The rotation angle for the first TextBlock is increased by 0.2° every frame I calculated this by knowing that the phone display is refreshed at 30 frames per second Multiply 30 frames per second by 60 seconds per minute by 0.2° and you get 360 ° The rotation angle for the second... collection: Silverlight Project: UIThreadVsRenderThread File: MainPage.xaml (excerpt) < /phone: PhoneApplicationPage.Resources> The MainPage constructor starts the animation going... issues Although you can use these on the phone emulator, the emulator tends to run faster than the actual phone, so you’re not getting a true view of performance anyway For more details about performance issues, you’ll want to study the document “Creating High Performance Silverlight Applications for Windows Phone available online The Settings class in the System .Windows. Interop namespace has three Boolean... some level the Silverlight animations are making use of something equivalent to CompositionTarget.Rendering, right? 5 26 Actually, that’s not true It’s pretty much true for the desktop version of Silverlight, but Silverlight for Windows Phone has been enhanced to make greater use of the phone s graphics processing unit (GPU) Although GPUs are customarily associated with hardware accelerations of complex... File: MainPage.xaml (excerpt) ... Storyboard.TargetName="rotate3" Storyboard.TargetProperty="Angle" From="0" To=" 360 " Duration="0:0:0.5" /> < /phone: PhoneApplicationPage.Resources> Three buttons; three storyboards Notice the attached properties: To set the attached properties in code, you make calls... The calculation for the frame-based animation assumes that the CompositionTarget.Rendering handler is called at the rate of 30 times per second However, the phone I’m using for this book has a video refresh rate closer to 27 frames per second—about 27. 35, to be more precise That’s one problem with frame-based animation: It’s dependent on the actual hardware Your mileage may vary Here’s another difference:... - 1); double radius = Math.Min(center.X, center.Y); Polyline polyline = new Polyline(); polyline.Stroke = this.Resources["PhoneForegroundBrush"] as Brush; polyline.StrokeThickness = 3; for (double angle = 0; angle < 360 0; angle += 0.25) { double scaledRadius = radius * angle / 360 0; double radians = Math.PI * angle / 180; double x = center.X + scaledRadius * Math.Cos(radians); double y = center.Y +... button, so it has less distance to travel to reach 360 but the same amount of time to do it in But does it really work? After the animation has concluded, try clicking the button again Nothing happens! The animation has left the Angle property at a value of 360 , so there’s nothing for subsequent animations to do! 519 Now try this: anima.From = - 360 ; anima.To = null; The To property setting might look... Grid.Row="0" Text="UI Thread" FontSize="{StaticResource PhoneFontSizeLarge}" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5 0.5"> 5 27 . return; int mask = -1 67 7 72 16; // ie, FF000000 for (int clr = 0; clr < 3; clr++) mask |= (byte)(0xFF << (8 - bitSelectDialog.ColorBits[clr])) << ( 16 - 8 * clr); for (int. MainPage.xaml.cs (excerpt) public partial class MainPage : PhoneApplicationPage, ISaveFileDialogCompleted { PhoneApplicationService appService = PhoneApplicationService.Current; PhotoChooserTask photoChooser. rotate1.Angle = (rotate1.Angle + 0.2) % 360 ; // Time-based TimeSpan elapsedTime = DateTime.Now - startTime; rotate2.Angle = (elapsedTime.TotalMinutes * 360 ) % 360 ; } void OnButtonClick(object

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

Tài liệu cùng người dùng

Tài liệu liên quan