xaml - Custom WPF TextBox with attached unit TextBlock -
my application has tons of textbox
controls user can enter numeric values. of these values in physical unit. unit indicator displayed @ right side of textbox
control.
that looks following sketch: [________] km (where unit "km")
currently have done stackpanel
instances everywhere. it's same pattern. makes xaml less readable should be.
i'm looking textbox
control includes textblock
@ side display unit.
my first try class derived textbox
, xaml file replaces template
property this:
<textbox x:class="wpfapplication1.unittextbox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:name="_this" keyboardnavigation.istabstop="false" style="{staticresource {x:type textbox}}"> <textbox.template> <controltemplate> <grid> <grid.columndefinitions> <columndefinition width="*"/> <columndefinition width="auto"/> </grid.columndefinitions> <textbox foreground="{templatebinding foreground}" isenabled="{templatebinding isenabled}" isreadonly="{binding isreadonly, elementname=_this}" style="{templatebinding style}" text="{binding text, elementname=_this}" width="{templatebinding width}" ... lots more ... verticalalignment="center"/> <textblock grid.column="1" text="{binding unit, elementname=_this}" margin="4,0,0,0" verticalalignment="center"/> </grid> </controltemplate> </textbox.template> </textbox>
unit
dependency property in unittextbox
code-behind class:
public partial class unittextbox : textbox { public static dependencyproperty unitproperty = dependencyproperty.register( name: "unit", propertytype: typeof(string), ownertype: typeof(unittextbox)); public string unit { { return (string) getvalue(unitproperty); } set { setvalue(unitproperty, value); } } public unittextbox() { initializecomponent(); } }
unfortunately, there's number of issues approach. need pass through virtually properties inner textbox
can see (i abbreviated here). also, i'd width
property apply inner textbox
usual, not outer grid
. think need separate property , bind inner textbox
instance that. , currently, style set when using unittextbox
class ignored. don't know how solve that.
is there possibility create such combined control wpf? should act textbox
event handlers, bindable properties etc., include unit string in appearance, assignable additional property.
could instead use custom style
adds textblock
somewhere around (but think need outer grid
aligning things), , declare unit attached property?
the annoying thing extending templated control typically need define new template each system theme, or customized textbox
out of place next regular textbox
. however, since "enhancement" simple, can avoid entirely overriding layout , rendering code include unit text:
public class unittextbox : textbox { private formattedtext _unittext; private rect _unittextbounds; public static dependencyproperty unittextproperty = dependencyproperty.register( name: "unittext", propertytype: typeof(string), ownertype: typeof(unittextbox), typemetadata: new frameworkpropertymetadata( default(string), frameworkpropertymetadataoptions.affectsmeasure | frameworkpropertymetadataoptions.affectsarrange | frameworkpropertymetadataoptions.affectsrender)); public string unittext { { return (string)getvalue(unittextproperty); } set { setvalue(unittextproperty, value); } } public static dependencyproperty unitpaddingproperty = dependencyproperty.register( name: "unitpadding", propertytype: typeof(thickness), ownertype: typeof(unittextbox), typemetadata: new frameworkpropertymetadata( new thickness(5d, 0d, 0d, 0d), frameworkpropertymetadataoptions.affectsmeasure | frameworkpropertymetadataoptions.affectsarrange | frameworkpropertymetadataoptions.affectsrender)); public thickness unitpadding { { return (thickness)getvalue(unitpaddingproperty); } set { setvalue(unitpaddingproperty, value); } } public static dependencyproperty textboxwidthproperty = dependencyproperty.register( name: "textboxwidth", propertytype: typeof(double), ownertype: typeof(unittextbox), typemetadata: new frameworkpropertymetadata( double.nan, frameworkpropertymetadataoptions.affectsmeasure)); public double textboxwidth { { return (double)getvalue(textboxwidthproperty); } set { setvalue(textboxwidthproperty, value); } } protected override void onpropertychanged(dependencypropertychangedeventargs e) { base.onpropertychanged(e); if (e.property == foregroundproperty) ensureunittext(invalidate: true); } protected override size measureoverride(size constraint) { var textboxwidth = this.textboxwidth; var unit = ensureunittext(invalidate: true); var padding = this.unitpadding; if (unit != null) { var unitwidth = unit.width + padding.left + padding.right; var unitheight = unit.height + padding.top + padding.bottom; constraint = new size( constraint.width - unitwidth, math.max(constraint.height, unitheight)); } var hasfixedtextboxwidth = !double.isnan(textboxwidth) && !double.isinfinity(textboxwidth); if (hasfixedtextboxwidth) constraint = new size(textboxwidth, constraint.height); var basesize = base.measureoverride(constraint); var basewidth = hasfixedtextboxwidth ? textboxwidth : basesize.width; if (unit != null) { var unitwidth = unit.width + padding.left + padding.right; var unitheight = unit.height + padding.top + padding.bottom; return new size( basewidth + unitwidth, math.max(basesize.height, unitheight)); } return new size(basewidth, basesize.height); } protected override size arrangeoverride(size arrangebounds) { var textsize = arrangebounds; var unit = ensureunittext(invalidate: false); var padding = this.unitpadding; if (unit != null) { var unitwidth = unit.width + padding.left + padding.right; var unitheight = unit.height + padding.top + padding.bottom; textsize.width -= unitwidth; _unittextbounds = new rect( textsize.width + padding.left, (arrangebounds.height - unitheight) / 2 + padding.top, textsize.width, textsize.height); } var basesize = base.arrangeoverride(textsize); if (unit != null) { var unitwidth = unit.width + padding.left + padding.right; var unitheight = unit.height + padding.top + padding.bottom; return new size( basesize.width + unitwidth, math.max(basesize.height, unitheight)); } return basesize; } protected override void onrender(drawingcontext drawingcontext) { base.onrender(drawingcontext); var unittext = ensureunittext(invalidate: false); if (unittext != null) drawingcontext.drawtext(unittext, _unittextbounds.location); } private formattedtext ensureunittext(bool invalidate = false) { if (invalidate) _unittext = null; if (_unittext != null) return _unittext; var unit = this.unittext; if (!string.isnullorempty(unit)) { _unittext = new formattedtext( unit, cultureinfo.invariantculture, this.flowdirection, new typeface( this.fontfamily, this.fontstyle, this.fontweight, this.fontstretch), this.fontsize, this.foreground); } return _unittext; } }
the textboxwidth
property lets set fixed width textbox
. behavior of width
remains unchanged, should: governs size of entire control, e.g., textbox
and unit text.
no custom style or template necessary.
Comments
Post a Comment