Child control handling touch event affects multi-point manipulation - wpf

I have a UserControl that must respond to TouchUp events and this sits within a Viewbox which needs to be panned and scaled with pinch manipulation. Touch events on the control are handled fine. However pinch manipulations only scale the ViewPort if both pinch points are contained entirely within either the user control or the Viewport space around it. If the pinch straddles the user control boundary then the ManipulationDelta loses one of the points and reports a scale of (1,1).
If I remove IsManipulationEnabled="True" from the control handling the TouchUp event then the scaling works but the touch event doesn’t fire.
What can I do to retain the manipulation across the ViewPort whilst also handling the touch event in the user control?
Test Solution
<Window x:Class="TouchTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Touch Test"
Height="400"
Width="700"
ManipulationDelta="OnManipulationDelta"
ManipulationStarting="OnManipulationStarting">
<Grid Background="Transparent"
IsManipulationEnabled="True">
<Viewbox x:Name="Viewbox"
Stretch="Uniform">
<Viewbox.RenderTransform>
<MatrixTransform/>
</Viewbox.RenderTransform>
<Grid Width="800"
Height="800"
Background="LightGreen"
IsManipulationEnabled="True"
TouchUp="OnTouchUp">
<TextBlock x:Name="TimeTextBlock"
FontSize="100"
TextAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Viewbox>
<TextBlock x:Name="ScaleTextBlock"
FontSize="10"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"/>
</Grid>
</Window>
Handlers in code-behind:
private void OnTouchUp(object sender, TouchEventArgs e)
{
TimeTextBlock.Text = DateTime.Now.ToString("H:mm:ss.fff");
}
private void OnManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = this;
}
private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
if (Viewbox == null)
{
return;
}
ManipulationDelta delta = e.DeltaManipulation;
ScaleTextBlock.Text = $"Delta Scale: {delta.Scale}";
MatrixTransform transform = Viewbox.RenderTransform as MatrixTransform;
if (transform == null)
{
return;
}
Matrix matrix = transform.Matrix;
Point position = ((FrameworkElement)e.ManipulationContainer).TranslatePoint(e.ManipulationOrigin, Viewbox);
position = matrix.Transform(position);
matrix = MatrixTransformations.ScaleAtPoint(matrix, delta.Scale.X, delta.Scale.Y, position);
matrix = MatrixTransformations.PreventNegativeScaling(matrix);
matrix = MatrixTransformations.Translate(matrix, delta.Translation);
matrix = MatrixTransformations.ConstrainOffset(Viewbox.RenderSize, matrix);
transform.Matrix = matrix;
}
Supporting class:
public static class MatrixTransformations
{
/// <summary>
/// Prevent the transformation from being offset beyond the given size rectangle.
/// </summary>
/// <param name="size"></param>
/// <param name="matrix"></param>
/// <returns></returns>
public static Matrix ConstrainOffset(Size size, Matrix matrix)
{
double distanceBetweenViewRightEdgeAndActualWindowRight = size.Width * matrix.M11 - size.Width + matrix.OffsetX;
double distanceBetweenViewBottomEdgeAndActualWindowBottom = size.Height * matrix.M22 - size.Height + matrix.OffsetY;
if (distanceBetweenViewRightEdgeAndActualWindowRight < 0)
{
// Moved in the x-axis too far left. Snap back to limit
matrix.OffsetX -= distanceBetweenViewRightEdgeAndActualWindowRight;
}
if (distanceBetweenViewBottomEdgeAndActualWindowBottom < 0)
{
// Moved in the x-axis too far left. Snap back to limit
matrix.OffsetY -= distanceBetweenViewBottomEdgeAndActualWindowBottom;
}
// Prevent positive offset
matrix.OffsetX = Math.Min(0.0, matrix.OffsetX);
matrix.OffsetY = Math.Min(0.0, matrix.OffsetY);
return matrix;
}
/// <summary>
/// Prevent the transformation from performing a negative scale.
/// </summary>
/// <param name="matrix"></param>
/// <returns></returns>
public static Matrix PreventNegativeScaling(Matrix matrix)
{
matrix.M11 = Math.Max(1.0, matrix.M11);
matrix.M22 = Math.Max(1.0, matrix.M22);
return matrix;
}
/// <summary>
/// Translate the matrix by the given vector to providing panning.
/// </summary>
/// <param name="matrix"></param>
/// <param name="vector"></param>
/// <returns></returns>
public static Matrix Translate(Matrix matrix, Vector vector)
{
matrix.Translate(vector.X, vector.Y);
return matrix;
}
/// <summary>
/// Scale the matrix by the given X/Y factors centered at the given point.
/// </summary>
/// <param name="matrix"></param>
/// <param name="scaleX"></param>
/// <param name="scaleY"></param>
/// <param name="point"></param>
/// <returns></returns>
public static Matrix ScaleAtPoint(Matrix matrix, double scaleX, double scaleY, Point point)
{
matrix.ScaleAt(scaleX, scaleY, point.X, point.Y);
return matrix;
}
}

So, I'm not a wpf programmer. But have a suggestion/workaround which could possibly work for you.
You could code the thing as follows:
set IsManipulationEnabled="True" (in this case OnTouchUp isn't fired for the grid colored in LightGreen)
Set OnTouchUp to fire on either Viewbox x:Name="Viewbox" or the Grid above this Viewbox (rather than for the 800x800 Grid)
So now OnTouchUp would be fired whenever you touch anywhere in the Viewbox (not just inside the LightGreen area)
When OnTouchUp is now fired, just check if the co-ordinates are in the region of LightGreen box. If YES-> update the time, if no, leave the time as it is.
I understand this is a workaround. Still posted an answer, in case it could prove useful.

i am not sure the sample you post reflect totally your code...but what i see : you do not manage the ManipulationCompleted and LostMouseCapture. Also you do not make any MouseCapture() MouseRelease() so when the manipulation outbound the window you loose it....search "mouse capture" on this repo, you will see even if no manipulation event that this is quite complicated....https://github.com/TheCamel/ArchX/search?utf8=%E2%9C%93&q=mouse+capture&type=

Related

Slider \ ScrollViewer in a touch interface not working properly

In WPF I've got the following XAML:
<ScrollViewer Canvas.Left="2266" Canvas.Top="428" Height="378" Name="scrollViewer1" Width="728" PanningMode="VerticalOnly" PanningRatio="2">
<Canvas Height="1732.593" Width="507.667">
<Slider Height="40.668" x:Name="slider1" Width="507.667" Style="{DynamicResource SliderStyle1}" Canvas.Left="15" Canvas.Top="150" />
</Slider>
</Canvas>
</ScrollViewer>
It's a ScrollViewer containing a Slider. I'm using the following on a touch-screen, and I'm using the panning even to scroll the ScrollViewer vertically. When PanningMode="VerticalOnly" is set, the slider stops working!
I'm assuming the ScollViewer is consuming the touch\slide event and handling it before the slider does (but I think I'm wrong on this front).
Is there any workaround for this?
I just solved this issue in our app.
What is happening is that the ScrollViewer captures the TouchDevice in its PreviewTouchMove handler, which "steals" the TouchDevice from other controls and prevents them from receiving any PreviewTouchMove or TouchMove events.
In order to work around this, you need to implement a custom Thumb control that captures the TouchDevice in the PreviewTouchDown event and stores a reference to it until the PreviewTouchUp event occurs. Then the control can "steal" the capture back in its LostTouchCapture handler, when appropriate. Here is some brief code:
public class CustomThumb : Thumb
{
private TouchDevice currentDevice = null;
protected override void OnPreviewTouchDown(TouchEventArgs e)
{
// Release any previous capture
ReleaseCurrentDevice();
// Capture the new touch
CaptureCurrentDevice(e);
}
protected override void OnPreviewTouchUp(TouchEventArgs e)
{
ReleaseCurrentDevice();
}
protected override void OnLostTouchCapture(TouchEventArgs e)
{
// Only re-capture if the reference is not null
// This way we avoid re-capturing after calling ReleaseCurrentDevice()
if (currentDevice != null)
{
CaptureCurrentDevice(e);
}
}
private void ReleaseCurrentDevice()
{
if (currentDevice != null)
{
// Set the reference to null so that we don't re-capture in the OnLostTouchCapture() method
var temp = currentDevice;
currentDevice = null;
ReleaseTouchCapture(temp);
}
}
private void CaptureCurrentDevice(TouchEventArgs e)
{
bool gotTouch = CaptureTouch(e.TouchDevice);
if (gotTouch)
{
currentDevice = e.TouchDevice;
}
}
}
Then you will need to re-template the Slider to use the CustomThumb instead of the default Thumb control.
i strugled with a similar issue. the workaround was this one (none of the others worked for me): i created a custom thumb, and then i used it inside a scrollbar style in xaml as the PART_Track's thumb.
public class DragableThumb : Thumb
{
double m_originalOffset;
double m_originalDistance;
int m_touchID;
/// <summary>
/// Get the parent scrollviewer, if any
/// </summary>
/// <returns>Scroll viewer or null</returns>
ScrollViewer GetScrollViewer()
{
if (TemplatedParent is ScrollBar && ((ScrollBar)TemplatedParent).TemplatedParent is ScrollViewer)
{
return ((ScrollViewer)((ScrollBar)TemplatedParent).TemplatedParent);
}
return null;
}
/// <summary>
/// Begin thumb drag
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnTouchDown(TouchEventArgs e)
{
ScrollViewer scrollViewer;
base.OnTouchDown(e);
m_touchID = e.TouchDevice.Id;
if ((scrollViewer = GetScrollViewer()) != null)
{
m_originalOffset = scrollViewer.HorizontalOffset;
m_originalDistance = e.GetTouchPoint(scrollViewer).Position.X;
}
}
/// <summary>
/// Handle thumb delta
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnTouchMove(TouchEventArgs e)
{
ScrollViewer scrollViewer;
double actualDistance;
base.OnTouchMove(e);
if ((scrollViewer = GetScrollViewer()) != null && m_touchID == e.TouchDevice.Id)
{
actualDistance = e.GetTouchPoint(scrollViewer).Position.X;
scrollViewer.ScrollToHorizontalOffset(m_originalOffset + (actualDistance - m_originalDistance) * scrollViewer.ExtentWidth / scrollViewer.ActualWidth);
}
}
}
The following worked for me. I searched around for a long time for something that would work. I adapted this for touch from How to make WPF Slider Thumb follow cursor from any point. This is a much simpler fix and allows you to avoid creating a custom slider/thumb control.
<Slider TouchMove="OnTouchMove" IsMoveToPointEnabled="True"/>
IsMoveToPointEnable must be set to true for this to work.
private void Slider_OnTouchMove(object sender, TouchEventArgs e)
{
Slider slider = (Slider)sender;
TouchPoint point = e.GetTouchPoint (slider );
double d = 1.0 / slider.ActualWidth * point.Position.X;
int p = int(slider.Maximum * d);
slider.Value = p;
}
This is nice and simple and worked for me - although it's worth wrapping up in a generic function and extending to handle the slider minimum value also as it may not be zero. What a pain to have to do though. There are many thing about WPF that are cool, but so many simple things require extra steps it really can be detrimental to productivity.

WPF ListView columns dont update thair width in time [duplicate]

I have a ListView WPF control with a GridView. I'd like to resize the GridView columns when the content of the columns changes.
I have several distinct data sets but when I change from one to another, the size of each columns fits the previous data. I'd like to update dynamically. How can I do that?
Finally, some results on this one. I've found a way to do the same auto-sizing that is done initially and when the gripper on a column header is double clicked.
public void AutoSizeColumns()
{
GridView gv = listView1.View as GridView;
if (gv != null)
{
foreach (var c in gv.Columns)
{
// Code below was found in GridViewColumnHeader.OnGripperDoubleClicked() event handler (using Reflector)
// i.e. it is the same code that is executed when the gripper is double clicked
if (double.IsNaN(c.Width))
{
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}
}
Building on top of Oskars answer, here is a blend behavior to automatically size the columns when the content changes.
/// <summary>
/// A <see cref="Behavior{T}"/> implementation which will automatically resize the automatic columns of a <see cref="ListView">ListViews</see> <see cref="GridView"/> to the new content.
/// </summary>
public class GridViewColumnResizeBehaviour : Behavior<ListView>
{
/// <summary>
/// Listens for when the <see cref="ItemsControl.Items"/> collection changes.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
var listView = AssociatedObject;
if (listView == null)
return;
AddHandler(listView.Items);
}
private void AddHandler(INotifyCollectionChanged sourceCollection)
{
Contract.Requires(sourceCollection != null);
sourceCollection.CollectionChanged += OnListViewItemsCollectionChanged;
}
private void RemoveHandler(INotifyCollectionChanged sourceCollection)
{
Contract.Requires(sourceCollection != null);
sourceCollection.CollectionChanged -= OnListViewItemsCollectionChanged;
}
private void OnListViewItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
var listView = AssociatedObject;
if (listView == null)
return;
var gridView = listView.View as GridView;
if (gridView == null)
return;
// If the column is automatically sized, change the column width to re-apply automatic width
foreach (var column in gridView.Columns.Where(column => Double.IsNaN(column.Width)))
{
Contract.Assume(column != null);
column.Width = column.ActualWidth;
column.Width = Double.NaN;
}
}
/// <summary>
/// Stops listening for when the <see cref="ItemsControl.Items"/> collection changes.
/// </summary>
protected override void OnDetaching()
{
var listView = AssociatedObject;
if (listView != null)
RemoveHandler(listView.Items);
base.OnDetaching();
}
}
If like me you are old and prefer VB.NET then here's Oskars code:
Public Sub AutoSizeColumns()
Dim gv As GridView = TryCast(Me.listview1.View, GridView)
If gv IsNot Nothing Then
For Each c As GridViewColumn In gv.Columns
If Double.IsNaN(c.Width) Then
c.Width = c.ActualWidth
End If
c.Width = Double.NaN
Next
End If
End Sub
This works great in WPF, finally someone has worked this out. Thanks Oskar.
Isn't there a way to bind to the ActualWidth of the column? Something like :
<GridViewColumn x:Name="column1" Width="{Binding ElementName=column1, Path=ActualWidth}" />
I have tried this and it works only the first time, it seems. No binding error.
You could measure the longest string in pixels and then adjust the column widths accordingly:
Graphics graphics = this.CreateGraphics();
SizeF textSize = graphics.MeasureString("How long am I?", this.Font);
If you create an algorithm for sizing each column as a ratio of these lengths, you should get a good result.

WPF Validation: Clearing all validation errors

I have a WPF UserControl with many other controls inside of it.
TextBoxes are among these.
Every TextBox has its own validation:
<TextBox>
<TextBox.Text>
<Binding Path="MyPath" StringFormat="{}{0:N}" NotifyOnValidationError="True">
<Binding.ValidationRules>
<r:MyValidationRule ValidationType="decimal" />
</Binding.ValidationRules>
</Binding>
<TextBox.Text>
<TextBox>
a
Now suppose the user types some invalid characters into them. They will all become highlighted red.
Now I want to reset all the validation errors (from the incorrect input) and set the recent correct values coming from DataContext.
I set the DataContext in the constructor and I don't want to change it (DataContext = null won't help me then):
DataContext = _myDataContext = new MyDataContext(..);
What I've already found are these classes:
Validation.ClearInvalid(..)
BindingExpression.UpdateTarget();
I think these classes could help me, but they require the Binding of a concrete FrameworkElement and I want to do it globally for all of them.
Should I anyhow iterate through the Visual Tree (which is really what I don't like) or is there any better solution for this?
This is what a BindingGroup is for... You'd set a BindingGroup on a container of all the controls, e.g. the panel that contains them. This would cause the updates to the DataContext to be held until you call UpdateSources on the BindingGroup. If you want to reset the user's input, you'd call CancelEdit instead, and the BindingGroup would reset all controls inside the container to the (still unchanged) values of the DataContext.
I had the same problem. Multiple validated controls on a page. I found/made this solution to update (and clear all validation from) the descentents of a DependencyObject:
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
/// <summary>
/// Updates all binding targets where the data item is of the specified type.
/// </summary>
/// <param name="root">The root.</param>
/// <param name="depth">The depth.</param>
/// <param name="dataItemType">Type of the data item.</param>
/// <param name="clearInvalid">Clear validation errors from binding.</param>
public static void UpdateAllBindingTargets(this DependencyObject root, int depth, Type dataItemType, bool clearInvalid)
{
var bindingExpressions = EnumerateDescendentsBindingExpressions(root, depth);
foreach (BindingExpression be in bindingExpressions.Where(be => be.DataItem != null && be.DataItem.GetType() == dataItemType))
{
if (be != null)
{
be.UpdateTarget();
if (clearInvalid)
System.Windows.Controls.Validation.ClearInvalid(be);
}
}
}
/// <summary>
/// Enumerates all binding expressions on descendents.
/// </summary>
/// <param name="root">The root.</param>
/// <param name="depth">The depth.</param>
/// <returns></returns>
public static IEnumerable<BindingExpression> EnumerateDescendentsBindingExpressions(this DependencyObject root, int depth)
{
return root.EnumerateDescendents(depth).SelectMany(obj => obj.EnumerateBindingExpressions());
}
/// <summary>
/// Enumerates the descendents of the specified root to the specified depth.
/// </summary>
/// <param name="root">The root.</param>
/// <param name="depth">The depth.</param>
public static IEnumerable<DependencyObject> EnumerateDescendents(this DependencyObject root, int depth)
{
int count = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
yield return child;
if (depth > 0)
{
foreach (var descendent in EnumerateDescendents(child, --depth))
yield return descendent;
}
}
}
/// <summary>
/// Enumerates the binding expressions of a Dependency Object.
/// </summary>
/// <param name="element">The parent element.</param>
public static IEnumerable<BindingExpression> EnumerateBindingExpressions(this DependencyObject element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
LocalValueEnumerator lve = element.GetLocalValueEnumerator();
while (lve.MoveNext())
{
LocalValueEntry entry = lve.Current;
if (BindingOperations.IsDataBound(element, entry.Property))
{
if (entry.Value is PriorityBindingExpression)
{
foreach (BindingExpression expr in ((PriorityBindingExpression)entry.Value).BindingExpressions)
yield return expr;
}
else if (entry.Value is MultiBindingExpression)
{
foreach (BindingExpression expr in ((MultiBindingExpression)entry.Value).BindingExpressions)
yield return expr;
}
else
yield return entry.Value as BindingExpression;
}
}
}
Why won't you just trigger NotifyPropertyChanged for all the properties of your data source? This will update binding and UI controls should get values from datacontext (which are valid, thus validation errors will be cleared)?
I'm not sure what you mean by
I set the DataContext in the constructor and I don't want to change it
(DataContext = null won't help me then)
Generally to reset all bindings on the form you do the following: (assuming a controller for views/viewmodel wiring, otherwise just use a code-behind on the view.)
var dataContext = view.DataContext;
view.DataContext = null;
view.DataContext = dataContext;
It doesn't change it to a new data context, it just drops the data context and reloads it. This kicks off all of the bindings to re-load.
Although hbarck gave a perfectly correct answer, I would just like to add that for many standard WPF controls, BindingGroups are created automatically. Therefore, in most cases, the following simple code is enough for clearing all validation errors inside some control (for example, DataGrid):
foreach (var bg in BindingOperations.GetSourceUpdatingBindingGroups(myDataGrid))
bg.CancelEdit();

Validation in textbox in WPF

I am currently working on a WPF application where I would like to have a TextBox that can only have numeric entries in it. I know that I can validate the content of it when I lost the focus and block the content from being numeric, but in other Windows Form application, we use to totally block any input except numerical from being written down. Plus, we use to put that code in a separate dll to reference it in many places.
Here is the code in 2008 not using WPF:
Public Shared Sub BloquerInt(ByRef e As System.Windows.Forms.KeyPressEventArgs, ByRef oTxt As Windows.Forms.TextBox, ByVal intlongueur As Integer)
Dim intLongueurSelect As Integer = oTxt.SelectionLength
Dim intPosCurseur As Integer = oTxt.SelectionStart
Dim strValeurTxtBox As String = oTxt.Text.Substring(0, intPosCurseur) & oTxt.Text.Substring(intPosCurseur + intLongueurSelect, oTxt.Text.Length - intPosCurseur - intLongueurSelect)
If IsNumeric(e.KeyChar) OrElse _
Microsoft.VisualBasic.Asc(e.KeyChar) = System.Windows.Forms.Keys.Back Then
If Microsoft.VisualBasic.AscW(e.KeyChar) = System.Windows.Forms.Keys.Back Then
e.Handled = False
ElseIf strValeurTxtBox.Length < intlongueur Then
e.Handled = False
Else
e.Handled = True
End If
Else
e.Handled = True
End If
Is there an equivalent way in WPF? I wouldn't mind if this is in a style but I am new to WPF so style are a bit obscure to what they can or can't do.
You can restrict the input to numbers only using an attached property on the TextBox. Define the attached property once (even in a separate dll) and use it on any TextBox. Here is the attached property:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
/// <summary>
/// Class that provides the TextBox attached property
/// </summary>
public static class TextBoxService
{
/// <summary>
/// TextBox Attached Dependency Property
/// </summary>
public static readonly DependencyProperty IsNumericOnlyProperty = DependencyProperty.RegisterAttached(
"IsNumericOnly",
typeof(bool),
typeof(TextBoxService),
new UIPropertyMetadata(false, OnIsNumericOnlyChanged));
/// <summary>
/// Gets the IsNumericOnly property. This dependency property indicates the text box only allows numeric or not.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
/// <returns>The value of the StatusBarContent property</returns>
public static bool GetIsNumericOnly(DependencyObject d)
{
return (bool)d.GetValue(IsNumericOnlyProperty);
}
/// <summary>
/// Sets the IsNumericOnly property. This dependency property indicates the text box only allows numeric or not.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
/// <param name="value">value of the property</param>
public static void SetIsNumericOnly(DependencyObject d, bool value)
{
d.SetValue(IsNumericOnlyProperty, value);
}
/// <summary>
/// Handles changes to the IsNumericOnly property.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
/// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
private static void OnIsNumericOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool isNumericOnly = (bool)e.NewValue;
TextBox textBox = (TextBox)d;
if (isNumericOnly)
{
textBox.PreviewTextInput += BlockNonDigitCharacters;
textBox.PreviewKeyDown += ReviewKeyDown;
}
else
{
textBox.PreviewTextInput -= BlockNonDigitCharacters;
textBox.PreviewKeyDown -= ReviewKeyDown;
}
}
/// <summary>
/// Disallows non-digit character.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="TextCompositionEventArgs"/> that contains the event data.</param>
private static void BlockNonDigitCharacters(object sender, TextCompositionEventArgs e)
{
foreach (char ch in e.Text)
{
if (!Char.IsDigit(ch))
{
e.Handled = true;
}
}
}
/// <summary>
/// Disallows a space key.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="KeyEventArgs"/> that contains the event data.</param>
private static void ReviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
// Disallow the space key, which doesn't raise a PreviewTextInput event.
e.Handled = true;
}
}
}
Here is how to use it (replace "controls" with your own namespace):
<TextBox controls:TextBoxService.IsNumericOnly="True" />
You can put a validation in your binding
<TextBox>
<TextBox.Text>
<Binding Path="CategoriaSeleccionada.ColorFondo"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<utilities:RGBValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Look at this example (of my program), you put the validation inside the binding like this. With UpdateSourceTrigger you can change when you binding will be updated (lost focus, in every change...)
Well, the validation is a class, I will put you an example:
class RGBValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// Here you make your validation using the value object.
// If you want to check if the object is only numbers you can
// Use some built-in method
string blah = value.ToString();
int num;
bool isNum = int.TryParse(blah, out num);
if (isNum) return new ValidationResult(true, null);
else return new ValidationResult(false, "It's no a number");
}
}
In short, do the job inside that method and return a new ValidationResult. The first parameter is a bool, true if the validation is good, false if not. The second parameter is only a message for information.
I think that this is the basics of textbox validation.
Hope this help.
EDIT: Sorry, I don't know VB.NET but I think that the C# code is pretty simple.

Formatting text in WinForm Label

Is it possible to format certain text in a WinForm Label instead of breaking the text into multiple labels? Please disregard the HTML tags within the label's text; it's only used to get my point out.
For example:
Dim myLabel As New Label
myLabel.Text = "This is <b>bold</b> text. This is <i>italicized</i> text."
Which would produce the text in the label as:
This is bold text. This is
italicized text.
That's not possible with a WinForms label as it is. The label has to have exactly one font, with exactly one size and one face. You have a couple of options:
Use separate labels
Create a new Control-derived class that does its own drawing via GDI+ and use that instead of Label; this is probably your best option, as it gives you complete control over how to instruct the control to format its text
Use a third-party label control that will let you insert HTML snippets (there are a bunch - check CodeProject); this would be someone else's implementation of #2.
Not really, but you could fake it with a read-only RichTextBox without borders. RichTextBox supports Rich Text Format (rtf).
Another workaround, late to the party: if you don't want to use a third party control, and you're just looking to call attention to some of the text in your label, and you're ok with underlines, you can use a LinkLabel.
Note that many consider this a 'usability crime', but if you're not designing something for end user consumption then it may be something you're prepared to have on your conscience.
The trick is to add disabled links to the parts of your text that you want underlined, and then globally set the link colors to match the rest of the label. You can set almost all the necessary properties at design-time apart from the Links.Add() piece, but here they are in code:
linkLabel1.Text = "You are accessing a government system, and all activity " +
"will be logged. If you do not wish to continue, log out now.";
linkLabel1.AutoSize = false;
linkLabel1.Size = new Size(365, 50);
linkLabel1.TextAlign = ContentAlignment.MiddleCenter;
linkLabel1.Links.Clear();
linkLabel1.Links.Add(20, 17).Enabled = false; // "government system"
linkLabel1.Links.Add(105, 11).Enabled = false; // "log out now"
linkLabel1.LinkColor = linkLabel1.ForeColor;
linkLabel1.DisabledLinkColor = linkLabel1.ForeColor;
Result:
Worked solution for me - using custom RichEditBox. With right properties it will be looked as simple label with bold support.
1) First, add your custom RichTextLabel class with disabled caret :
public class RichTextLabel : RichTextBox
{
public RichTextLabel()
{
base.ReadOnly = true;
base.BorderStyle = BorderStyle.None;
base.TabStop = false;
base.SetStyle(ControlStyles.Selectable, false);
base.SetStyle(ControlStyles.UserMouse, true);
base.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
base.MouseEnter += delegate(object sender, EventArgs e)
{
this.Cursor = Cursors.Default;
};
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x204) return; // WM_RBUTTONDOWN
if (m.Msg == 0x205) return; // WM_RBUTTONUP
base.WndProc(ref m);
}
}
2) Split you sentence to words with IsSelected flag, that determine if that word should be bold or no :
private void AutocompleteItemControl_Load(object sender, EventArgs e)
{
RichTextLabel rtl = new RichTextLabel();
rtl.Font = new Font("MS Reference Sans Serif", 15.57F);
StringBuilder sb = new StringBuilder();
sb.Append(#"{\rtf1\ansi ");
foreach (var wordPart in wordParts)
{
if (wordPart.IsSelected)
{
sb.Append(#"\b ");
}
sb.Append(ConvertString2RTF(wordPart.WordPart));
if (wordPart.IsSelected)
{
sb.Append(#"\b0 ");
}
}
sb.Append(#"}");
rtl.Rtf = sb.ToString();
rtl.Width = this.Width;
this.Controls.Add(rtl);
}
3) Add function for convert you text to valid rtf (with unicode support!) :
private string ConvertString2RTF(string input)
{
//first take care of special RTF chars
StringBuilder backslashed = new StringBuilder(input);
backslashed.Replace(#"\", #"\\");
backslashed.Replace(#"{", #"\{");
backslashed.Replace(#"}", #"\}");
//then convert the string char by char
StringBuilder sb = new StringBuilder();
foreach (char character in backslashed.ToString())
{
if (character <= 0x7f)
sb.Append(character);
else
sb.Append("\\u" + Convert.ToUInt32(character) + "?");
}
return sb.ToString();
}
Works like a charm for me!
Solutions compiled from :
How to convert a string to RTF in C#?
Format text in Rich Text Box
How to hide the caret in a RichTextBox?
Create the text as a RTF file in wordpad
Create Rich text control with no borders and editable = false
Add the RTF file to the project as a resource
In the Form1_load do
myRtfControl.Rtf = Resource1.MyRtfControlText
AutoRichLabel
I was solving this problem by building an UserControl that contains a TransparentRichTextBox that is readonly. The TransparentRichTextBox is a RichTextBox that allows to be transparent:
TransparentRichTextBox.cs:
public class TransparentRichTextBox : RichTextBox
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern IntPtr LoadLibrary(string lpFileName);
protected override CreateParams CreateParams
{
get
{
CreateParams prams = base.CreateParams;
if (TransparentRichTextBox.LoadLibrary("msftedit.dll") != IntPtr.Zero)
{
prams.ExStyle |= 0x020; // transparent
prams.ClassName = "RICHEDIT50W";
}
return prams;
}
}
}
The final UserControl acts as wrapper of the TransparentRichTextBox. Unfortunately, I had to limit it to AutoSize on my own way, because the AutoSize of the RichTextBox became broken.
AutoRichLabel.designer.cs:
partial class AutoRichLabel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.rtb = new TransparentRichTextBox();
this.SuspendLayout();
//
// rtb
//
this.rtb.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.rtb.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtb.Location = new System.Drawing.Point(0, 0);
this.rtb.Margin = new System.Windows.Forms.Padding(0);
this.rtb.Name = "rtb";
this.rtb.ReadOnly = true;
this.rtb.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None;
this.rtb.Size = new System.Drawing.Size(46, 30);
this.rtb.TabIndex = 0;
this.rtb.Text = "";
this.rtb.WordWrap = false;
this.rtb.ContentsResized += new System.Windows.Forms.ContentsResizedEventHandler(this.rtb_ContentsResized);
//
// AutoRichLabel
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.BackColor = System.Drawing.Color.Transparent;
this.Controls.Add(this.rtb);
this.Name = "AutoRichLabel";
this.Size = new System.Drawing.Size(46, 30);
this.ResumeLayout(false);
}
#endregion
private TransparentRichTextBox rtb;
}
AutoRichLabel.cs:
/// <summary>
/// <para>An auto sized label with the ability to display text with formattings by using the Rich Text Format.</para>
/// <para>­</para>
/// <para>Short RTF syntax examples: </para>
/// <para>­</para>
/// <para>Paragraph: </para>
/// <para>{\pard This is a paragraph!\par}</para>
/// <para>­</para>
/// <para>Bold / Italic / Underline: </para>
/// <para>\b bold text\b0</para>
/// <para>\i italic text\i0</para>
/// <para>\ul underline text\ul0</para>
/// <para>­</para>
/// <para>Alternate color using color table: </para>
/// <para>{\colortbl ;\red0\green77\blue187;}{\pard The word \cf1 fish\cf0 is blue.\par</para>
/// <para>­</para>
/// <para>Additional information: </para>
/// <para>Always wrap every text in a paragraph. </para>
/// <para>Different tags can be stacked (i.e. \pard\b\i Bold and Italic\i0\b0\par)</para>
/// <para>The space behind a tag is ignored. So if you need a space behind it, insert two spaces (i.e. \pard The word \bBOLD\0 is bold.\par)</para>
/// <para>Full specification: http://www.biblioscape.com/rtf15_spec.htm </para>
/// </summary>
public partial class AutoRichLabel : UserControl
{
/// <summary>
/// The rich text content.
/// <para>­</para>
/// <para>Short RTF syntax examples: </para>
/// <para>­</para>
/// <para>Paragraph: </para>
/// <para>{\pard This is a paragraph!\par}</para>
/// <para>­</para>
/// <para>Bold / Italic / Underline: </para>
/// <para>\b bold text\b0</para>
/// <para>\i italic text\i0</para>
/// <para>\ul underline text\ul0</para>
/// <para>­</para>
/// <para>Alternate color using color table: </para>
/// <para>{\colortbl ;\red0\green77\blue187;}{\pard The word \cf1 fish\cf0 is blue.\par</para>
/// <para>­</para>
/// <para>Additional information: </para>
/// <para>Always wrap every text in a paragraph. </para>
/// <para>Different tags can be stacked (i.e. \pard\b\i Bold and Italic\i0\b0\par)</para>
/// <para>The space behind a tag is ignored. So if you need a space behind it, insert two spaces (i.e. \pard The word \bBOLD\0 is bold.\par)</para>
/// <para>Full specification: http://www.biblioscape.com/rtf15_spec.htm </para>
/// </summary>
[Browsable(true)]
public string RtfContent
{
get
{
return this.rtb.Rtf;
}
set
{
this.rtb.WordWrap = false; // to prevent any display bugs, word wrap must be off while changing the rich text content.
this.rtb.Rtf = value.StartsWith(#"{\rtf1") ? value : #"{\rtf1" + value + "}"; // Setting the rich text content will trigger the ContentsResized event.
this.Fit(); // Override width and height.
this.rtb.WordWrap = this.WordWrap; // Set the word wrap back.
}
}
/// <summary>
/// Dynamic width of the control.
/// </summary>
[Browsable(false)]
public new int Width
{
get
{
return base.Width;
}
}
/// <summary>
/// Dynamic height of the control.
/// </summary>
[Browsable(false)]
public new int Height
{
get
{
return base.Height;
}
}
/// <summary>
/// The measured width based on the content.
/// </summary>
public int DesiredWidth { get; private set; }
/// <summary>
/// The measured height based on the content.
/// </summary>
public int DesiredHeight { get; private set; }
/// <summary>
/// Determines the text will be word wrapped. This is true, when the maximum size has been set.
/// </summary>
public bool WordWrap { get; private set; }
/// <summary>
/// Constructor.
/// </summary>
public AutoRichLabel()
{
InitializeComponent();
}
/// <summary>
/// Overrides the width and height with the measured width and height
/// </summary>
public void Fit()
{
base.Width = this.DesiredWidth;
base.Height = this.DesiredHeight;
}
/// <summary>
/// Will be called when the rich text content of the control changes.
/// </summary>
private void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
{
this.AutoSize = false; // Disable auto size, else it will break everything
this.WordWrap = this.MaximumSize.Width > 0; // Enable word wrap when the maximum width has been set.
this.DesiredWidth = this.rtb.WordWrap ? this.MaximumSize.Width : e.NewRectangle.Width; // Measure width.
this.DesiredHeight = this.MaximumSize.Height > 0 && this.MaximumSize.Height < e.NewRectangle.Height ? this.MaximumSize.Height : e.NewRectangle.Height; // Measure height.
this.Fit(); // Override width and height.
}
}
The syntax of the rich text format is quite simple:
Paragraph:
{\pard This is a paragraph!\par}
Bold / Italic / Underline text:
\b bold text\b0
\i italic text\i0
\ul underline text\ul0
Alternate color using color table:
{\colortbl ;\red0\green77\blue187;}
{\pard The word \cf1 fish\cf0 is blue.\par
But please note: Always wrap every text in a paragraph. Also, different tags can be stacked (i.e. \pard\b\i Bold and Italic\i0\b0\par) and the space character behind a tag is ignored. So if you need a space behind it, insert two spaces (i.e. \pard The word \bBOLD\0 is bold.\par). To escape \ or { or }, please use a leading \.
For more information there is a full specification of the rich text format online.
Using this quite simple syntax you can produce something like you can see in the first image. The rich text content that was attached to the RtfContent property of my AutoRichLabel in the first image was:
{\colortbl ;\red0\green77\blue187;}
{\pard\b BOLD\b0 \i ITALIC\i0 \ul UNDERLINE\ul0 \\\{\}\par}
{\pard\cf1\b BOLD\b0 \i ITALIC\i0 \ul UNDERLINE\ul0\cf0 \\\{\}\par}
If you want to enable word wrap, please set the maximum width to a desired size. However, this will fix the width to the maximum width, even when the text is shorter.
Have fun!
There is an excellent article from 2009 on Code Project named "A Professional HTML Renderer You Will Use" which implements something similar to what the original poster wants.
I use it successfully within several projects of us.
Very simple solution:
Add 2 labels on the form, LabelA and LabelB
Go to properties for LabelA and dock it to left.
Go to properties for LabelB and dock it to left as well.
Set Font to bold for LabelA .
Now the LabelB will shift depending on length of text of LabelA.
That's all.
I Would also be interested in finding out if it is possible.
When we couldn't find a solution we resorted to Component Ones 'SuperLabel' control which allows HTML markup in a label.
Realising this is an old question, my answer is more for those, like me, who still may be looking for such solutions and stumble upon this question.
Apart from what was already mentioned, DevExpress's LabelControl is a label that supports this behaviour - demo here. Alas, it is part of a paid library.
If you're looking for free solutions, I believe HTML Renderer is the next best thing.
A FlowLayoutPanel works well for your problem. If you add labels to the flow panel and format each label's font and margin properties, then you can have different font styles. Pretty quick and easy solution to get working.
Yeah.
You can implements, using HTML Render.
For you see, click on the link: https://htmlrenderer.codeplex.com/
I hope this is useful.

Resources