Here we will build a UserControl (user control) to step through how to customize the control in WPF and introduce some of the new features of WPF into a custom control.
We have produced a clock control with a voice chime function, with the following effects:
Right-click your project in VS, click "Add New Item", select "UserControl" in the list of choices that appears, VS will automatically generate a *.xaml file for you and its corresponding background code file (*.cs or other).
It is worth noting that in the auto-generated code, your control is inherited from the System.Windows.Controls.UserControl class, which corresponds to your control and is not necessarily the most appropriate base class, you can modify it, but notice that you should also modify * The base class in the. cs file and the *.xaml file, not just the *.cs file, or the project will be generated with an error "not inheriting from the same base class". Modify the *.xaml file by the "UserControl" of the first and last line of the file: Change to the name of the base class that you think is appropriate.
1, adding properties to the control (dependency property, DependencyProperty)
As shown in the following code:
public static readonly DependencyProperty Timeproperty =
Dependencyproperty.register ("Time", typeof (DateTime), typeof (Clockuserctrl),
New Frameworkpropertymetadata (Datetime.now,new propertychangedcallback (Timepropertychangedcallback)));
The dependency property that we add to the control (or any one of the WPF classes) is "public", "Static", "read-only", and is named "Property name +property", which is a constant way of writing dependent properties. The registration of a dependency property can be called when the property is declaredDependencyproperty.register() method is registered, or it can be registered in its statically constructed method. The aboveDependencyproperty.registerSeveral parameters of the method are: the property name (the property name is less than the "attribute" suffix compared to the declared dependency property name "Xxxproperty"), the other is exactly the same, otherwise the exception is reported at run time, the data type of the property, the type of the property's owner, and the metadata.
About the metadata passed in the parameter: if it is a normal class, it should be passedPropertyMetadata, and if it is frameworkelement, it can be passedFrameworkpropertymetadata, whereFrameworkpropertymetadataYou can make some markup that indicates how the control should react when the property changes, such as when a property's changes affect the drawing of the control, then the metadata for that property should be written like this:New Frameworkpropertymetadata (Defaulevalue, Frameworkpropertymetadataoptions.affectsrender); This allows the system to consider redrawing the control when the property changes. In addition, the metadata also protects a lot of content, such as default values, data validation, callback functions when data changes, whether to participate in attribute "inheritance", etc.
We then wrap the dependency property as a normal property:
[Description ("Get or set current date and time")]
[Category ("Common Properties")]
Public DateTime Time
{
get
{
return (DateTime) this. GetValue (Timeproperty);
}
set
{
this. SetValue (Timeproperty, value);
}
}
The GetValue and SetValue methods are derived from the DependencyObject class, which is used to get or set a property value for a class.
Note: When wrapping a dependency property as a normal property, do not perform any other operations in the get and set blocks except for the step-by-GetValue and SetValue methods. The following code is inappropriate :
[Description ("Get or set current date and time")]
[Category ("Common Properties")]
Public DateTime Time
{
Get
{
return (DateTime) this. GetValue (Timeproperty);
}
set
{
this. SetValue (Timeproperty, value);
this. ontimeupdated (value);//error
}
}
In the past, this may have been a lot of people's usual notation. , but in WPF, there are potential errors in this notation for the following reasons: We know that classes that inherit from DependencyObject have GetValue and SetValue methods to get or set property values, so why don't we use that method directly to get or set the property value, and to wrap it up as a normal. NET attribute, in fact, both of these ways are possible, but packaged into ordinary. NET properties are more consistent with the habits of. NET developers, using GetValue and SetValue are more like the habits of Java developers, but XAML is not invoked when it executes as if it were a Java developer. NET attribute instead of using the GetValue or SetValue method directly, so that the other code that we write in the get and set blocks is not executed at all by the XAML. So, in terms of the time property above, C # (or other) calls to that property do not have any problems. However, when this property is used in XAML (such as in XAML for data binding to this property), the this. ontimeupdated (value); The statement will not be executed to.
Then, when the time property changes, you do need to call this. ontimeupdated (value); statement (because the statement raises the event that the time was updated) or the dependency property metadata passed:
new Frameworkpropertymetadata (Datetime.now,new propertychangedcallback ( Timepropertychangedcallback), we specify a callback function for the change of the property, which is executed when the property changes:
private static void Timepropertychangedcallback (DependencyObject sender, DependencyPropertyChangedEventArgs Arg)
{
if (Sender != null && sender is clockuserctrl)
{
ClockUserCtrl clock = sender as ClockUserCtrl;
clock. ontimeupdated (DateTime) Arg. oldvalue, (DateTime) Arg. NewValue);
}
}
2, add an event for the control (circular event, RoutedEvent)
The way to add a circular event is similar to adding a dependency property:
public static readonly RoutedEvent timeupdatedevent =
Eventmanager.registerroutedevent ("timeupdated",
Routingstrategy.bubble, typeof (Routedpropertychangedeventhandler<datetime>), typeof (Clockuserctrl));
The supporting methods eventmanager.registerroutedevent() correspond to several parameters: event name, the way the event is circulated (upward circulation, downward circulation or not circulated), the type of EventHandler the event corresponds to, Type of event owner)
The event is then packaged as normal. NET event:
[Description ("occurs after the date or time has been updated")]
Public event Routedpropertychangedeventhandler<datetime> timeupdated
{
add
{
this. AddHandler (Timeupdatedevent, value);
}
remove
{
this. RemoveHandler (Timeupdatedevent, value);
}
}
Note that, as with dependency properties, do not add code except AddHandler and RemoveHandler in Add and remove blocks.
Off-topic, E in the event argument. Handled=true is not an end-of-event circulation, it is just a token for the event so that, by default, those event handlers are not called when the token is true, to register a processing method for an event that is marked true, and to have the method executed. Please use the AddHandler method and set the last parameter Handlereventstoo to True, as follows:
This.myInkCanvas.AddHandler (
Inkcanvas.mouseleftbuttondownevent,
New Mousebuttoneventhandler (
Myinkcanvas_mouseleftbuttondown),
true);
private void Myinkcanvas_mouseleftbuttondown (
Object sender, MouseButtonEventArgs e)
{
Do something
}
Then write the usual OnXxx method:
protected virtual void ontimeupdated (DateTime oldValue, datetime newvalue)
{
routedpropertychangedeventargs<datetime> arg =
New Routedpropertychangedeventargs<datetime> (OldValue, newvalue,timeupdatedevent);
This. RaiseEvent (ARG);
}
3, adding commands to the control (Commands)
The ability to add commands like WPF built-in controls for custom controls is a good thing (in fact, it's also a way to reduce the coupling between the interface and the back-end logic in CustomControl, which will be described in the next article in this series of essays).
There are two main types of commands built into WPF: RoutedCommand and Routeduicommand, which have more than one text property to automatically localize the text of the command on the interface, and more can refer to commands and command bindings in WPF (a) and command-and-command binding in WPF (ii).
Here we define a command whose function is the voice chime of the control. First we define a command:
public static readonly Routeduicommand Speakcommand = new Routeduicommand ("Speak", "Speak", typeof (Clockuserctrl));
The parameters are named display names, the name of the command, and the owner type of the command.
A command binding is then defined in the static function of the control, which defines the specifics of the command: what is the corresponding command? What functions does it perform in the current environment?
Commandbinding commandbinding =
New Commandbinding (Speakcommand, New Executedroutedeventhandler (Executespeak),
New Canexecuteroutedeventhandler (Canexecutespeak)); private static void Executespeak (object sender, Executedroutedeventargs arg)
{
Clockuserctrl Clock = sender as Clockuserctrl;
if (clock! = null)
{
clock. Speakthetime ();
}
}
private static void canexecutespeak ( Object sender, canexecuteroutedeventargs arg)
{
ClockUserCtrl clock = sender as ClockUserCtrl;
arg. canexecute = (Clock != null);
}
The CanExecute property of the Canexecuteroutedeventargs is used to indicate whether the current command is available, which means that the command and its object are constantly being viewed by the system. And according to the conditions you provided to determine whether the current command is available, such as the text box state becomes "read-only" after its "paste" command will not be available, the Paste button for the text box is automatically disabled, and vice versa is enabled.
The new Executedroutedeventhandler (executespeak) delegate specifies the task to complete when the command is executed, which is implemented by the callback Excutespeak function.
private static void Executespeak (object sender, Executedroutedeventargs arg)
{
Clockuserctrl Clock = sender as Clockuserctrl;
if (clock! = null)
{
Clock. Speakthetime ();
}
}private void Speakthetime ()
{
DateTime localtime = this. Time.tolocaltime ();
String texttospeak = "moment now," +
Localtime.toshortdatestring () + "," +
Localtime.toshorttimestring () +
", Week" + (int) Localtime.dayofweek;
This.speecher.SpeakAsync (Texttospeak);
}
We can also add shortcut keys for commands, which are implemented by InputBinding, which associates commands with shortcut keys for commands, such as:
InputBinding inputbinding = new InputBinding (Speakcommand, New Mousegesture (Mouseaction.leftclick));
Commandmanager.registerclassinputbinding (typeof (Clockuserctrl), inputbinding);
This will cause the control's speak command to be raised when the mouse clicks on the control, thus calling the Speakthetime function for voice broadcast.
Shortcut keys can be defined by mousegesture or keygesture.
4, Advantages and disadvantages:
As mentioned in the custom Control in WPF (1), UserControl can quickly build custom controls, but it lacks good support for template styling, and the controls are not as flexible as WPF built-in controls, and in the next chapter of this series of essays, We'll show you how to build a CustomControl that provides full support for new WPF features.
DEMO
UserControl in WPF