Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 95 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
95
Dung lượng
2,46 MB
Nội dung
CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 830 GetTemplateChild to find the button defined by its actual template. If this exists, it adds an event handler to the button’s Click event. The code for the control is as follows: using System.Windows; using System.Windows.Controls; using System.Windows.Markup; using Microsoft.Win32; namespace Apress.VisualCSharpRecipes.Chapter17 { [TemplatePart(Name = "PART_Browse", Type = typeof(Button))] [ContentProperty("FileName")] public class FileInputControl : Control { static FileInputControl() { DefaultStyleKeyProperty.OverrideMetadata( typeof(FileInputControl), new FrameworkPropertyMetadata( typeof(FileInputControl))); } public override void OnApplyTemplate() { base.OnApplyTemplate(); Button browseButton = base.GetTemplateChild("PART_Browse") as Button; if (browseButton != null) browseButton.Click += new RoutedEventHandler(browseButton_Click); } void browseButton_Click(object sender, RoutedEventArgs e) { OpenFileDialog dlg = new OpenFileDialog(); if (dlg.ShowDialog() == true) { this.FileName = dlg.FileName; } } public string FileName { get { return (string)GetValue(FileNameProperty); } CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 831 set { SetValue(FileNameProperty, value); } } public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register( "FileName", typeof(string), typeof(FileInputControl)); } } The default style and control template for FileInputControl is in a ResourceDictionary in the Themes subfolder and is merged into the Generic ResourceDictionary. The XAML for this style is as follows: <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Apress.VisualCSharpRecipes.Chapter17;assembly="> <Style TargetType="{x:Type local:FileInputControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:FileInputControl}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <DockPanel> <Button x:Name="PART_Browse" DockPanel.Dock="Right" Margin="2,0,0,0"> Browse </Button> <TextBox IsReadOnly="True" Text="{Binding Path=FileName, RelativeSource= {RelativeSource TemplatedParent}}" /> </DockPanel> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> The XAML for the window that consumes this custom control is as follows: <Window x:Class="Apress.VisualCSharpRecipes.Chapter17.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Apress.VisualCSharpRecipes.Chapter17;assembly=" CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 832 Title="Recipe17_14" Height="200" Width="300"> <StackPanel> <StackPanel.Resources> <Style x:Key="fileInputStyle"> <Setter Property="Control.Height" Value="50" /> <Setter Property="Control.FontSize" Value="20px" /> <Setter Property="Control.BorderBrush" Value="Blue" /> <Setter Property="Control.BorderThickness" Value="2" /> <Style.Triggers> <Trigger Property="Control.IsMouseOver" Value="True"> <Setter Property="Control.BorderThickness" Value="3" /> <Setter Property="Control.BorderBrush" Value="RoyalBlue" /> </Trigger> </Style.Triggers> </Style> <ControlTemplate x:Key="fileInputTemplate" TargetType="{x:Type local:FileInputControl}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <DockPanel> <Button x:Name="PART_Browse" DockPanel.Dock="Left" Background="Lightgreen"> <TextBlock FontSize="20px" Padding="3px" FontFamily="Arial" Text="Open "/> </Button> <TextBlock x:Name="PART_Text" VerticalAlignment="Center" Margin="5, 0, 0, 0" FontSize="16px" FontWeight="Bold" Text="{Binding Path=FileName, RelativeSource= {RelativeSource TemplatedParent}}" /> </DockPanel> </Border> </ControlTemplate> </StackPanel.Resources> <! Use the default appearance > <local:FileInputControl Margin="8" /> <! Applying a style to the control > <local:FileInputControl Margin="8" Style="{StaticResource fileInputStyle}" /> <! Applying a template to the control > <local:FileInputControl Margin="8" Template="{StaticResource fileInputTemplate}" /> </StackPanel> </Window> CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 833 Figure 17-11. Creating and using a FileInput custom control 17-15. Create a Two-Way Binding Problem You need to create a two-way binding so that when the value of either property changes, the other one automatically updates to reflect it. Solution Use the System.Windows.Data.Binding markup extension, and set the Mode attribute to System.Windows. Data.BindingMode.TwoWay. Use the UpdateSourceTrigger attribute to specify when the binding source should be updated. How It Works The data in a binding can flow from the source property to the target property, from the target property to the source property, or in both directions. For example, suppose the Text property of a System.Windows.Controls.TextBox control is bound to the Value property of a System.Windows. Controls.Slider control. In this case, the Text property of the TextBox control is the target of the binding, and the Value property of the Slider control is the binding source. The direction of data flow between the target and the source can be configured in a number of different ways. It could be configured such that when the Value of the Slider control changes, the Text property of the TextBox is updated. This is called a one-way binding. Alternatively, you could configure the binding so that when the Text property of the TextBox changes, the Slider control’s Value is automatically updated to reflect it. This is called a one-way binding to the source. A two-way binding means that a change to either the source property or the target property automatically updates the other. This type of binding is useful for editable forms or other fully interactive UI scenarios. It is the Mode property of a Binding object that configures its data flow. This stores an instance of the System.Windows.Data.BindingMode enumeration and can be configured with the values listed in Table 17-6. CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 834 Table 17-6. BindingMode Values for Configuring the Data Flow in a Binding Value Description Default The Binding uses the default Mode value of the binding target, which varies for each dependency property. In general, user-editable control properties, such as those of text boxes and check boxes, default to two-way bindings, whereas most other properties default to one-way bindings. OneTime The target property is updated when the control is first loaded or when the data context changes. This type of binding is appropriate if the data is static and won’t change once it has been set. OneWay The target property is updated whenever the source property changes. This is appropriate if the target control is read-only, such as a System.Windows.Controls.Label or System.Windows.Controls.TextBlock. If the target property does change, the source property will not be updated. OneWayToSource This is the opposite of OneWay. The source property is updated when the target property changes. TwoWay Changes to either the target property or the source automatically update the other. Bindings that are TwoWay or OneWayToSource listen for changes in the target property and update the source. It is the UpdateSourceTrigger property of the binding that determines when this update occurs. For example, suppose you created a TwoWay binding between the Text property of a TextBox control and the Value property of a Slider control. You could configure the binding so that the slider is updated either as soon as you type text into the TextBox or when the TextBox loses its focus. Alternatively, you could specify that the TextBox is updated only when you explicitly call the UpdateSource property of the System.Windows.Data.BindingExpression class. These options are configured by the Binding’s UpdateSourceTrigger property, which stores an instance of the System.Windows.Data. UpdateSourceTrigger enumeration. Table 17-7 lists the possible values of this enumeration. Therefore, to create a two-way binding that updates the source as soon as the target property changes, you need to specify TwoWay as the value of the Binding’s Mode attribute and PropertyChanged for the UpdateSourceTrigger attribute. ■ Note To detect source changes in OneWay and TwoWay bindings, if the source property is not a System. Windows.DependencyProperty, it must implement System.ComponentModel.INotifyPropertyChanged to notify the target that its value has changed. CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 835 Table 17-7. UpdateSourceTrigger Values for Configuring When the Binding Source Is Updated Value Description Default The Binding uses the default UpdateSourceTrigger of the binding target property. For most dependency properties, this is PropertyChanged, but for the TextBox.Text property, it is LostFocus. Explicit Updates the binding source only when you call the System.Windows.Data.BindingExpression.UpdateSource method. LostFocus Updates the binding source whenever the binding target element loses focus. PropertyChanged Updates the binding source immediately whenever the binding target property changes. The Code The following example demonstrates a window containing a System.Windows.Controls.Slider control and a System.Windows.Controls.TextBlock control. The XAML statement for the Text property of the TextBlock specifies a Binding statement that binds it to the Value property of the Slider control. In the binding statement, the Mode attribute is set to TwoWay, and the UpdateSourceTrigger attribute is set to PropertyChanged. This ensures that when a number from 1 to 100 is typed into the TextBox, the Slider control immediately updates its value to reflect it. The XAML for the window is as follows: <Window x:Class="Apress.VisualCSharpRecipes.Chapter17.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Recipe17_15" Height="100" Width="260"> <StackPanel> <Slider Name="slider" Margin="4" Interval="1" TickFrequency="1" IsSnapToTickEnabled="True" Minimum="0" Maximum="100"/> <StackPanel Orientation="Horizontal" > <TextBlock Width="Auto" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4" Text="Gets and sets the value of the slider:" /> <TextBox Width="40" HorizontalAlignment="Center" Margin="4" Text="{Binding ElementName=slider, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> </StackPanel> </Window> CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 836 Figure 17-12 shows the resulting window. Figure 17-12. Creating a two-way binding 17-16. Bind to a Command Problem You need to bind a System.Windows.Controls.Button control directly to a System.Windows.Input. ICommand. This enables you to execute custom logic when the Button is clicked, without having to handle its Click event and call a method. You can also bind the IsEnabled property of the Button to the ICommand object’s CanExecute method. Solution Create a class that implements ICommand, and expose an instance of it as a property on another class or business object. Bind this property to a Button control’s Command property. How It Works The Button control derives from the System.Windows.Controls.Primitives.ButtonBase class. This implements the System.Windows.Input.ICommandSource interface and exposes an ICommand property called Command. The ICommand interface encapsulates a unit of functionality. When its Execute method is called, this functionality is executed. The CanExecute method determines whether the ICommand can be executed in its current state. It returns True if the ICommand can be executed and returns False if not. To execute custom application logic when a Button is clicked, you would typically attach an event handler to its Click event. However, you can also encapsulate this custom logic in a command and bind it directly to the Button control’s Command property. This approach has several advantages. First, the IsEnabled property of the Button will automatically be bound to the CanExecute method of the ICommand. This means that when the CanExecuteChanged event is fired, the Button will call the command’s CanExecute method and refresh its own IsEnabled property dynamically. Second, the application functionality that should be executed when the Button is clicked does not have to reside in the code-behind for the window. This enables greater separation of presentation and business logic, which is always desirable in object-oriented programming in general, and even more so in WPF development, because it makes it easier for UI designers to work alongside developers without getting in each other’s way. CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 837 To bind the Command property of a Button to an instance of an ICommand, simply set the Path attribute to the name of the ICommand property, just as you would any other property. You can also optionally specify parameters using the CommandParameter attribute. This in turn can be bound to the properties of other elements and is passed to the Execute and CanExecute methods of the command. The Code The following example demonstrates a window containing three System.Windows.Controls.TextBox controls. These are bound to the FirstName, LastName, and Age properties of a custom Person object. The Person class also exposes an instance of the AddPersonCommand and SetOccupationCommand as read-only properties. There are two Button controls on the window that have their Command attribute bound to these command properties. Custom logic in the CanExecute methods of the commands specifies when the Buttons should be enabled or disabled. If the ICommand can be executed and the Button should therefore be enabled, the code in the CanExecute method returns True. If it returns False, the Button will be disabled. The Set Occupation Button control also binds its CommandParameter to the Text property of a System.Windows.Controls.ComboBox control. This demonstrates how to pass parameters to an instance of an ICommand. Figure 17-13 shows the resulting window. The XAML for the window is as follows: <Window x:Class="Apress.VisualCSharpRecipes.Chapter17.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Recipe17_16" Height="233" Width="300"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="70"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="40"/> <RowDefinition Height="34"/> <RowDefinition Height="30"/> </Grid.RowDefinitions> <TextBlock Margin="4" Text="First Name" VerticalAlignment="Center"/> <TextBox Text="{Binding Path=FirstName}" Margin="4" Grid.Column="1"/> CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 838 <TextBlock Margin="4" Text="Last Name" Grid.Row="1" VerticalAlignment="Center"/> <TextBox Margin="4" Text="{Binding Path=LastName}" Grid.Column="1" Grid.Row="1"/> <TextBlock Margin="4" Text="Age" Grid.Row="2" VerticalAlignment="Center"/> <TextBox Margin="4" Text="{Binding Path=Age}" Grid.Column="1" Grid.Row="2"/> <! Bind the Button to the Add Command > <Button Command="{Binding Path=Add}" Content="Add" Margin="4" Grid.Row="3" Grid.Column="2"/> <StackPanel Orientation="Horizontal" Grid.Column="2" Grid.Row="4"> <ComboBox x:Name="cboOccupation" IsEditable="False" Margin="4" Width="100"> <ComboBoxItem>Student</ComboBoxItem> <ComboBoxItem>Skilled</ComboBoxItem> <ComboBoxItem>Professional</ComboBoxItem> </ComboBox> <Button Command="{Binding Path=SetOccupation}" CommandParameter="{Binding ElementName=cboOccupation, Path=Text}" Content="Set Occupation" Margin="4" /> </StackPanel> <TextBlock Margin="4" Text="Status" Grid.Row="5" VerticalAlignment="Center"/> <TextBlock Margin="4" Text="{Binding Path=Status, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" FontStyle="Italic" Grid.Column="1" Grid.Row="5"/> </Grid> </Window> The code-behind for the window sets its DataContext property to a new Person object. The code for this is as follows: using System.Windows; namespace Apress.VisualCSharpRecipes.Chapter17 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 839 // Set the DataContext to a Person object this.DataContext = new Person() { FirstName = "Zander", LastName = "Harris" }; } } } The code for the Person, AddPersonCommand, and SetOccupationCommand classes are as follows: using System; using System.ComponentModel; using System.Windows.Input; namespace Apress.VisualCSharpRecipes.Chapter17 { public class Person : INotifyPropertyChanged { private string firstName; private int age; private string lastName; private string status; private string occupation; private AddPersonCommand addPersonCommand; private SetOccupationCommand setOccupationCommand; public string FirstName { get { return firstName; } set { if(firstName != value) { firstName = value; OnPropertyChanged("FirstName"); } } } public string LastName { get { return lastName; } [...]... = DragDropEffects.None; } } // Handles the Drop event for the Canvas Creates a new Label // and adds it to the Canvas at the location of the mouse pointer 860 CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION private void cvsSurface_Drop(object sender, DragEventArgs e) { // Create a new Label Label newLabel = new Label(); newLabel.Content = e.Data.GetData(DataFormats.Text); newLabel.FontSize = 14; // Add... from a System.Windows.Controls.ListBox to a System.Windows Controls.Canvas ■ Note Drag-and-drop is relatively simple to implement in WPF, but contains a lot of variations depending on what you are trying to do and what content you are dragging This example focuses on dragging content from a ListBox to a Canvas, but the principles are similar for other types of drag-and-drop operations and can be adapted... based on the value of isAlternate Style style = isAlternate ? AlternateStyle : DefaultStyle; // Invert the flag isAlternate = !isAlternate; return style; } } } Figure 17-18 shows the resulting window 857 CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION Figure 17-18 Changing the appearance of alternate rows 17-21 Drag Items from a List and Drop Them on a Canvas Problem You need to allow the user to drag... Binding to a list of data objects and specifying a DataTemplate 847 CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION 17-18 Bind to a Collection with the Master-Detail Pattern Problem You need to bind to the items in a data collection and display more information about the selected item For example, you might display a list of product names and prices on one side of the screen and a more detailed view of... the DataTemplate The XAML for the window is as follows: SystemParameters.MinimumHorizontalDragDistance || Math.Abs(position.Y - startDragPoint.Y) > SystemParameters.MinimumVerticalDragDistance) { // User is dragging, set up the DragDrop behavior DragDrop.DoDragDrop(draggedItem, draggedItem.Content, DragDropEffects.Copy); } } } } } 861 CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION Figure 17-19 Dragging items from a. .. Occupation { get { return occupation; } set { 840 CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION if(this.occupation != value) { this.occupation = value; OnPropertyChanged("Occupation"); } } } /// Gets an AddPersonCommand for data binding public AddPersonCommand Add { get { if(addPersonCommand == null) addPersonCommand = new AddPersonCommand(this); return addPersonCommand; } } /// Gets a SetOccupationCommand... System.Collections.ObjectModel; namespace Apress.VisualCSharpRecipes.Chapter17 { public class PersonCollection : ObservableCollection { public PersonCollection() { this.Add(new Person() { FirstName = "Sam", LastName = "Bourton", Age = 33, Occupation = "Engineer" }); this.Add(new Person() { FirstName = "Adam", LastName = "Freeman", Age = 37, Occupation = "Professional" }); 853 CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION... Figure 17-17 shows the resulting window 855 CHAPTER 17 ■ WINDOWS PRESENTATION FOUNDATION Figure 17-17 Changing a control’s appearance on mouseover 17-20 Change the Appearance of Alternate Items in a List Problem You need to give a different appearance to items in alternate rows of a System.Windows.Controls ListBox Solution Create a System.Windows.Controls.StyleSelector class, and... property is set to a local StyleSelector class called AlternatingRowStyleSelector This class has a property called AlternateStyle, which is set to a Style resource that changes the Background property of a ListBoxItem The AlternatingRowStyleSelector class overrides the SelectStyle property and returns either the default or the alternate Style, based on a Boolean flag The XAML for the window is as follows: . FirstName, LastName, and Age properties of a custom Person object. The Person class also exposes an instance of the AddPersonCommand and SetOccupationCommand as read-only properties. There are. System.Windows.Input; namespace Apress.VisualCSharpRecipes.Chapter17 { public class Person : INotifyPropertyChanged { private string firstName; private int age; private string lastName; private string. command can execute if there are valid values /// for the person's FirstName, LastName, and Age properties /// and if it hasn't already been executed and had its /// Status property