WinRt: Binding a RTF String to a RichEditBox - c#

Searched a long time to bind some RTF text to an RichEditBox Control on Windows Store Applications. Even it should function in TwoMay Binding Mode.
...

... finally I found the following solution. I created a inherited control from the original RichEditBox control with a DependencyProperty RtfText.
public class RichEditBoxExtended : RichEditBox
{
public static readonly DependencyProperty RtfTextProperty =
DependencyProperty.Register(
"RtfText", typeof (string), typeof (RichEditBoxExtended),
new PropertyMetadata(default(string), RtfTextPropertyChanged));
private bool _lockChangeExecution;
public RichEditBoxExtended()
{
TextChanged += RichEditBoxExtended_TextChanged;
}
public string RtfText
{
get { return (string) GetValue(RtfTextProperty); }
set { SetValue(RtfTextProperty, value); }
}
private void RichEditBoxExtended_TextChanged(object sender, RoutedEventArgs e)
{
if (!_lockChangeExecution)
{
_lockChangeExecution = true;
string text;
Document.GetText(TextGetOptions.None, out text);
if (string.IsNullOrWhiteSpace(text))
{
RtfText = "";
}
else
{
Document.GetText(TextGetOptions.FormatRtf, out text);
RtfText = text;
}
_lockChangeExecution = false;
}
}
private static void RtfTextPropertyChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var rtb = dependencyObject as RichEditBoxExtended;
if (rtb == null) return;
if (!rtb._lockChangeExecution)
{
rtb._lockChangeExecution = true;
rtb.Document.SetText(TextSetOptions.FormatRtf, rtb.RtfText);
rtb._lockChangeExecution = false;
}
}
}
This solution works for me - perhaps for others too. :-)
Known issues: strange behaviours in VirtualizingStackPanel.VirtualizationMode="Recycling"

Related

How to change Checkbox style and Background Color in Xamarin Forms

I am new at Xamarin forms and renderers, Can you please help me out in this,
Thank you in advance.
I have created a checkbox but I want to change its background color like in picture.
Now you can see in image default checkbox is there I want to change its background and style how can I do that?
<local:Checkbox x:Name="chkBrand" AutomationId="AutoIdCheckBox"
Content="{Binding LblBrand}"
Checked="{Binding Chk,Mode=TwoWay}">
</local:Checkbox>
Checkbox.cs
public class Checkbox : Xamarin.Forms.View
{
public static readonly BindableProperty CheckedProperty =
BindableProperty.Create("Checked",
typeof(bool),
typeof(Checkbox),
false, BindingMode.TwoWay, propertyChanged: OnCheckedPropertyChanged);
public static readonly BindableProperty ContentProperty =
BindableProperty.Create("Content",
typeof(string),
typeof(Checkbox),
"Content", BindingMode.OneWay);
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create("FontSize",
typeof(double),
typeof(Checkbox),
default(double), BindingMode.OneWay);
public bool Checked
{
get
{
return (bool)GetValue(CheckedProperty);
}
set
{
if (this.Checked != value)
{
SetValue(CheckedProperty, value);
if (CheckedChanged != null)
CheckedChanged.Invoke(this, new CheckedChangedEventArgs(value));
}
}
}
public string Content
{
get
{
return (string)GetValue(ContentProperty);
}
set
{
SetValue(ContentProperty, value);
}
}
public double FontSize
{
get
{
return (double)GetValue(FontSizeProperty);
}
set
{
SetValue(FontSizeProperty, value);
}
}
public event EventHandler<CheckedChangedEventArgs> CheckedChanged;
private static void OnCheckedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var checkBox = (Checkbox)bindable;
checkBox.Checked = newvalue != null ? (bool)newvalue : false;
}
}
and platform wise renderer are used. Adding UWP renderer for reference
public class CheckboxRenderer : ViewRenderer<Checkbox, CheckBox>
{
public new static void Init()
{
var temp = DateTime.Now;
}
protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if (Control == null)
{
var checkBox = new CheckBox();
checkBox.Checked += CheckBox_Checked;
checkBox.Unchecked += CheckBox_Unchecked;
SetNativeControl(checkBox);
}
if (e.NewElement != null)
{
Control.Content = e.NewElement.Content;
Control.IsChecked = e.NewElement.Checked;
Control.IsEnabled = e.NewElement.IsEnabled;
if (default(double) != e.NewElement.FontSize)
Control.FontSize = e.NewElement.FontSize;
}
}
private void CheckBox_Unchecked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Checked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Content(object sender)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
//case "IsVisible":
// Control.Hidden = Element.IsVisible;
// break;
case "IsEnabled":
Control.IsEnabled = Element.IsEnabled;
break;
case "Checked":
Control.IsChecked = Element.Checked;
break;
case "Content":
Control.Content = Element.Content;
break;
}
}
There are two ways to do this,
Use nuget package to show check box or
A simple way to change the style is to use the image control for checkbox.
Take two images as per your colour, one checked and other one is unchecked and do hide & show on click.

TestStack White doesn't find TextBox in WPF Application

I use TestStack White framework to automate testing of a WPF Application
It needs to open modal window and access TextBox in it. Everything works well, but White can't find Textbox, although it finds other elements of window
I tried the following lines of code:
TestStack.White.UIItems.TextBox TextBox = CreateBranch.Get<TestStack.White.UIItems.TextBox>(SearchCriteria.byAutomationId("Title"));
where CreateBranch is modal window
I also tried (SearchCriteria.All), (SearchCriteria.ByControlType) and nothing works
Coded UI tool finds this element well by AutomationID, but I need to do it in White
UISpy and other similar tools recognize this control and see its AutomationID
This textbox is custom control, here's code for it, I changed namespace name for privacy:
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Test.Wpf.Controls.XTextBox
{
[TemplatePart(Name = "PART_Watermark", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_Pasword", Type = typeof(TextBlock))]
public class XTextBox : TextBox
{
#region Static
static XTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(XTextBox), new FrameworkPropertyMetadata(typeof(XTextBox)));
}
#endregion //Static
#region Fields
private TextBlock PART_Watermark;
private TextBlock PART_Pasword;
#endregion //Fields
#region DependencyProperties
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
"Watermark",
typeof(String),
typeof(XTextBox),
new PropertyMetadata(String.Empty));
public static readonly DependencyProperty WatermarkVerticalAlignmentProperty = DependencyProperty.Register(
"WatermarkVerticalAlignment",
typeof(VerticalAlignment),
typeof(XTextBox),
new PropertyMetadata(VerticalAlignment.Stretch));
public static readonly DependencyProperty WatermarkForegroundProperty = DependencyProperty.Register(
"WatermarkForeground",
typeof(Brush),
typeof(XTextBox),
new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public static readonly DependencyProperty WatermarkFontSizeProperty = DependencyProperty.Register(
"WatermarkFontSize",
typeof(Double),
typeof(XTextBox),
new PropertyMetadata(12.0));
public static readonly DependencyProperty IsFloatingProperty = DependencyProperty.Register(
"IsFloating",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty IsAccessNegativeProperty = DependencyProperty.Register(
"IsAccessNegative",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(true));
public static readonly DependencyProperty IsDigitOnlyProperty = DependencyProperty.Register(
"IsDigitOnly",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
"MaxValue",
typeof(Single),
typeof(XTextBox),
new PropertyMetadata(Single.NaN));
public static readonly DependencyProperty IsPasswordProperty = DependencyProperty.Register(
"IsPassword",
typeof(Boolean),
typeof(XTextBox),
new PropertyMetadata(false));
public static readonly DependencyProperty VisibilityMainTextProperty = DependencyProperty.Register(
"VisibilityMainText",
typeof(Visibility),
typeof(XTextBox),
new PropertyMetadata(Visibility.Visible));
#endregion //DependencyProperties
#region Properties
[Description("Gets or sets the watermark title")]
public String Watermark
{
get { return (String)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
[Description("Gets or sets the watermark vertical alignment")]
public VerticalAlignment WatermarkVerticalAlignment
{
get { return (VerticalAlignment)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title color")]
public Brush WatermarkForeground
{
get { return (Brush)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the watermark title font size")]
public Double WatermarkFontSize
{
get { return (Double)GetValue(WatermarkVerticalAlignmentProperty); }
set { SetValue(WatermarkVerticalAlignmentProperty, value); }
}
[Description("Gets or sets the textbox floating mode")]
public Boolean IsFloating
{
get { return (Boolean)GetValue(IsFloatingProperty); }
set { SetValue(IsFloatingProperty, value); }
}
[Description("Gets or sets the textbox access of negative values")]
public Boolean IsAccessNegative
{
get { return (Boolean)GetValue(IsAccessNegativeProperty); }
set { SetValue(IsAccessNegativeProperty, value); }
}
[Description("Gets or sets the textbox chars type")]
public Boolean IsDigitOnly
{
get { return (Boolean)GetValue(IsDigitOnlyProperty); }
set { SetValue(IsDigitOnlyProperty, value); }
}
[Description("Gets or sets the max input value (enable in digit mode only)")]
public Single MaxValue
{
get { return (Single)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
[Description("Gets or sets the textbox is passwordbox")]
public Boolean IsPassword
{
get { return (Boolean)GetValue(IsPasswordProperty); }
set { SetValue(IsPasswordProperty, value); }
}
public Visibility VisibilityMainText
{
get { return (Visibility)GetValue(VisibilityMainTextProperty); }
set { SetValue(VisibilityMainTextProperty, value); }
}
#endregion //Properties
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PART_Watermark = GetTemplateChild("PART_Watermark") as TextBlock;
PART_Pasword = GetTemplateChild("PART_Pasword") as TextBlock;
SetWatermarkVisibility();
if (IsPassword)
{
VisibilityMainText = Visibility.Collapsed;
if (PART_Pasword != null)
{
PART_Pasword.Visibility = Visibility.Visible;
PART_Pasword.FontSize = 20;
}
}
else
{
VisibilityMainText = Visibility.Visible;
}
DataObject.AddPastingHandler(this, OnPaste);
}
protected void OnPaste(Object sender, DataObjectPastingEventArgs e)
{
try
{
var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
if (!isText) return;
var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as String;
if (!String.IsNullOrEmpty(text))
{
if (IsDigitOnly)
{
if (!IsAccessNegative)
{
var ch = text[0];
if (ch == 45)
{
e.CancelCommand();
}
}
for (int i = 0; i < text.Length; i++)
{
if (i == 0)
{
if (IsAccessNegative && text[i] == 45)
{
continue;
}
}
if (!Char.IsDigit(text[0]))
e.CancelCommand();
}
}
}
}
catch (Exception)
{
// ignored
e.Handled = true;
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
SetWatermarkVisibility();
if (IsPassword)
{
PART_Pasword.Text = new String('•', Text.Length);
}
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
SetWatermarkVisibility();
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
if (PART_Watermark != null)
{
PART_Watermark.Visibility = Visibility.Hidden;
}
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
if (e.Text.Length == 0)
{
e.Handled = true;
return;
}
if (IsDigitOnly)
{
var ch = e.Text[0];
if (!Char.IsDigit(ch) && ch != 8 && ch != 46)
{
if (!(IsAccessNegative && ch == 45))
e.Handled = true;
}
if (IsFloating)
{
if (ch == 46 && Text.IndexOf('.') != -1)
{
e.Handled = true;
return;
}
}
if (!IsAccessNegative)
{
if (ch == 45)
{
e.Handled = true;
}
}
}
}
#region Private
private void SetWatermarkVisibility()
{
if (PART_Watermark != null)
{
PART_Watermark.Visibility = (Text != String.Empty || IsKeyboardFocused)? Visibility.Hidden : Visibility.Visible;
}
}
#endregion
}
}
Screenshot from UISpy
Let me know if that works
TextBox = (TextBox)CreateBranch
.Get(SearchCriteria.ByAutomationId("Title").AndOfFramework(WindowsFramework.Wpf));
Edited after new source added
You have to create a specific AutomationPeer for your custom control and return it via the override of the method OnCreateAutomationPeer().
Your control is a subclass of the TextBox control so you can just return a new TextBoxAutomationPeer instance or create your custom AutomationPeer from it.
public class XTextBox : TextBox
{
...
protected override AutomationPeer OnCreateAutomationPeer()
{
return new XTextBoxAutomationPeer(this);
// or just use the TextBoxAutomationPeer
// return new TextBoxAutomationPeer(this);
}
...
}
The custom automation peer
public class XTextBoxAutomationPeer : TextBoxAutomationPeer
{
public XTextBoxAutomationPeer(XTextBox owner)
: base(owner)
{
}
protected override string GetClassNameCore()
{
return "XTextBox";
}
}
[SetUpFixture]
public class SETUP_THAT_WILL_GET_CALL_LATER
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
var applicationDirectory = TestContext.CurrentContext.TestDirectory;
var applicationPath = Path.Combine(applicationDirectory, #"..\..\..\, "your debug folder path here", "your application.exe here");
Application = Application.Launch(applicationPath);
Thread.Sleep(2000);
Window = Application.GetWindow("Title of your application", InitializeOption.WithCache);
}
[OneTimeTearDown()]
public void OneTimeTearDown()
{
Window.Dispose();
Application.Dispose();
}
public static Application Application;
public static Window Window;
}
Then in your test
[Test]
public void yourtest()
{
var textBox = SETUP_THAT_WILL_GET_CALL_LATER.**Window.Get<TextBox>("Your textbox name here");**
}

How to create numeric Textbox Custom Control with dependency Property in WPF?

I want create a custom control for Numeric Text box with dependency property in WPF , in my solution , I add one WPF application and custom control (WPF) ,then in public class , I create dependency property ....
Now I don't know how can i write my rule for text box and which event is true?
Another question : What is my rule for numeric text box , that this text box must be give number and . and Separating .this custom Text box is for accounting system.
public static readonly DependencyProperty NumberTextbox;
static Numeric()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Numeric), new FrameworkPropertyMetadata(typeof(Numeric)));
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata("Enter Your Text", OnKeyDown);
NumberTextbox =DependencyProperty.Register("Text", typeof(TextBox), typeof(FrameworkElement), metadata);
}
public string NumberTXT
{
get { return (string)GetValue(NumberTextbox); }
set { SetValue(NumberTextbox, value); }
}
I recommend to you add another Dependency Property in example code below I named it Value
Also format your number by comma or NumberFormatInfo.CurrentInfo.NumberDecimalSeparator
and control caret location changes by two property SelectionLength and SelectionStart.
Finally for more detail and complete code WPF Maskable Number Entry TextBox
region Value property
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(NumericTextBox), new PropertyMetadata(new Double(), OnValueChanged));
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
//var numericBoxControl = (NumericTextBox)sender;
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); Text = value.ToString("###,###,###"); }
}
endregion
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
var txt = e.Text.Replace(",", "");
e.Handled = !IsTextAllowed(txt);
if (IsTextAllowed(txt))
{
if (Text.Length == 3)
{
Text = Text.Insert(1,",");
SelectionLength = 1;
SelectionStart += Text.Length;
}
}
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Back)
{
if (Text.Length == 5)
{
Text = Text.Replace(",", "");
SelectionLength = 1;
SelectionStart += Text.Length;
}
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
var txt = Text.Replace(",", "");
SetValue(ValueProperty, txt.Length==0?0:double.Parse(txt));
base.OnTextChanged(e);
}
private static bool IsTextAllowed(string text)
{
try
{
double.Parse(text);
return true;
}
catch (FormatException)
{
return false;
}
}
I don't understand your question exactly and why you need dependency proerties to make a numeric text box custom control. What you can do is to inherit from textbox and handle the PreviewTextInput, like it is solved in this question by Ray:
then you get:
public class NumericTextBox : TextBox
{
public NumericTextBox()
{
PreviewTextInput += NumericTextBox_PreviewTextInput;
}
void NumericTextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !isTextAllowed(e.Text);
}
private static bool isTextAllowed(string text)
{
var regex = new Regex("[^0-9]+");
return !regex.IsMatch(text);
}
}
And you can use it like that:
<myNameSpace:NumericTextBox />
And now you can add any other validation you want.
I would also implement a solution for the pasting issue, something like (see also in the link):
private void textBoxPasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(String)))
{
var text = (String)e.DataObject.GetData(typeof(String));
if (!isTextAllowed(text))
{
e.CancelCommand();
}
}
else
{
e.CancelCommand();
}
}
Good job, but let me do the following task with a UserControl in C #:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace NumericBox
{
public partial class NumericBox : TextBox
{
public NumericBox
{
this.TextAlign = HorizontalAlignment.Right;
this.Text = "0,00";
this.KeyPress += NumericBox_KeyPress;
}
public double NumericResult
{
get{
double d = Convert.ToDouble(this.Text.Replace('.', ','));
return d;
}
}
private void NumericBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar) && (e.KeyChar != '.'))
e.Handled = true;
if ((e.KeyChar == '.' && (sender as TextBox).Text.IndexOf('.') > -1))
e.Handled = true;
if (e.KeyChar == 13)
{
e.Handled = true;
SendKeys.Send("{TAB}");
}
}
}
}

wpf datagrid : create a DatagridNumericColumn in wpf

I have a requirement that I want to make a datagridcolumn which only accepts numeric values(integer) ,when the user enter something other than numbers handle the textbox .
I tried a lot of webpages ,Iam tired of these ,I greately appreciate anybody have the helping mind.
Based on #nit suggestion, you can create your own class derived from DataGridTextColumn like this:
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(System.Windows.FrameworkElement editingElement, System.Windows.RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
try
{
Convert.ToInt32(e.Text);
}
catch
{
// Show some kind of error message if you want
// Set handled to true
e.Handled = true;
}
}
}
In the PrepareCellForEdit method you register the OnPreviewTextInput method to the editing TextBox PreviewTextInput event, where you validate for numeric values.
In xaml, you simply use it:
<DataGrid ItemsSource="{Binding SomeCollection}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding NonNumericProperty}"/>
<local:DataGridNumericColumn Binding="{Binding NumericProperty}"/>
</DataGrid.Columns>
</DataGrid>
Hope this helps
Use TryParse instead, this helps to restrict input values to integer numbers only.
/// <summary>
/// This class help to create data grid cell which only support interger numbers.
/// </summary>
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
if (edit != null) edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
int value;
if (!int.TryParse(e.Text, out value))
e.Handled = true;
}
}
If you dont want to show any validation errors and just want to block any non-numeral value then you can create the DataGridTemplateColumn and in CellEditingTemplate use the TextBox.
<DataGridTemplateColumn Width="100*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=NumericProperty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox PreviewTextInput="TextBox_PreviewTextInput" Text="{Binding Path=NumericProperty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
and in PreviewTextInput of the TextBox set e.Handled = true if value is other than integer:
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
try
{
Convert.ToInt32(e.Text);
}
catch
{
e.Handled = true;
}
}
I got here looking for a solution to the same problem: constraining the input into cells on a DataGrid to be numeric. But the accepted answer did not work for me. The following did:
For the DataGrid add an event handler for PreparingForCellEdit.
In that event handler, cast the EditingElement to a TextBox and add an event handler for PreviewTextInput to the TextBox.
In the PreviewTextInput event handler set e.Handled to true, if the input should not be allowed.
The above steps work if the user clicks the cell to edit. However, if the cell is not in edit mode, the PreparingForCellEdit event will not be called. To perform validation in that case:
Add an event handler to the DataGrid for PreviewTextInput.
In that event handler, safely cast e.OriginalSource to a DataGridCell (exiting, if it is not a DataGridCell), check the DataGridCell's IsEditing property, and if the cell is not editing set e.Handled to true.
The effect of the above is that the user will have to click into the cell in order to edit its contents and, as such, the PreparingForCellEdit / PreviewTextInput combination above will be invoked for all changes to the cell's contents.
Just to extend #Omribitan's answer, Here is the solution with a data Paste guard added:
public class NumericTextColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var edit = editingElement as TextBox;
edit.PreviewTextInput += Edit_PreviewTextInput;
DataObject.AddPastingHandler(edit, OnPaste);
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
var data = e.SourceDataObject.GetData(DataFormats.Text);
if (!IsDataValid(data)) e.CancelCommand();
}
private void Edit_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !IsDataValid(e.Text);
}
bool IsDataValid(object data)
{
try
{
Convert.ToInt32(data);
return true;
}
catch
{
return false;
}
}
}
For whatever it's worth, here's how I solved it. This solution allows you to specify a variety of options when validating input, allows string formatting to be used (e.g. '$15.00' in the data grid) and more.
The null value and string formatting provided by the Binding class itself do not suffice as neither act correctly when the cell is editable so this class covers it. What this does is it uses another class that I've been using for a long time already: TextBoxInputBehavior, it has been an invaluable asset for me and it originally came from WPF – TextBox Input Behavior blog post albeit the version here seems much older (but well tested). So what I did I just transferred this existing functionality I already had on my TextBoxes to the my custom column and thus I have the same behaviour in both. Isn't that neat?
Here's the code of the custom column:
public class DataGridNumberColumn : DataGridTextColumn
{
private TextBoxInputBehavior _behavior;
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem);
// A clever workaround the StringFormat issue with the Binding set to the 'Binding' property. If you use StringFormat it
// will only work in edit mode if you changed the value, otherwise it will retain formatting when you enter editing.
if (!string.IsNullOrEmpty(StringFormat))
{
BindingOperations.ClearBinding(element, TextBlock.TextProperty);
BindingOperations.SetBinding(element, FrameworkElement.TagProperty, Binding);
BindingOperations.SetBinding(element,
TextBlock.TextProperty,
new Binding
{
Source = element,
Path = new PropertyPath("Tag"),
StringFormat = StringFormat
});
}
return element;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
if (!(editingElement is TextBox textBox))
return null;
var originalText = textBox.Text;
_behavior = new TextBoxInputBehavior
{
IsNumeric = true,
EmptyValue = EmptyValue,
IsInteger = IsInteger
};
_behavior.Attach(textBox);
textBox.Focus();
if (editingEventArgs is TextCompositionEventArgs compositionArgs) // User has activated editing by already typing something
{
if (compositionArgs.Text == "\b") // Backspace, it should 'clear' the cell
{
textBox.Text = EmptyValue;
textBox.SelectAll();
return originalText;
}
if (_behavior.ValidateText(compositionArgs.Text))
{
textBox.Text = compositionArgs.Text;
textBox.Select(textBox.Text.Length, 0);
return originalText;
}
}
if (!(editingEventArgs is MouseButtonEventArgs) || !PlaceCaretOnTextBox(textBox, Mouse.GetPosition(textBox)))
textBox.SelectAll();
return originalText;
}
private static bool PlaceCaretOnTextBox(TextBox textBox, Point position)
{
int characterIndexFromPoint = textBox.GetCharacterIndexFromPoint(position, false);
if (characterIndexFromPoint < 0)
return false;
textBox.Select(characterIndexFromPoint, 0);
return true;
}
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
UnwireTextBox();
base.CancelCellEdit(editingElement, uneditedValue);
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
UnwireTextBox();
return base.CommitCellEdit(editingElement);
}
private void UnwireTextBox() => _behavior.Detach();
public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
nameof(EmptyValue),
typeof(string),
typeof(DataGridNumberColumn));
public string EmptyValue
{
get => (string)GetValue(EmptyValueProperty);
set => SetValue(EmptyValueProperty, value);
}
public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
nameof(IsInteger),
typeof(bool),
typeof(DataGridNumberColumn));
public bool IsInteger
{
get => (bool)GetValue(IsIntegerProperty);
set => SetValue(IsIntegerProperty, value);
}
public static readonly DependencyProperty StringFormatProperty = DependencyProperty.Register(
nameof(StringFormat),
typeof(string),
typeof(DataGridNumberColumn));
public string StringFormat
{
get => (string) GetValue(StringFormatProperty);
set => SetValue(StringFormatProperty, value);
}
}
What I did is I peeked into the source code of DataGridTextColumn and handled the TextBox creation in almost the same way plus I attached the custom behaviour to the TextBox.
Here's the code of the behavior I attached (this is a behavior you can use on any TextBox):
public class TextBoxInputBehavior : Behavior<TextBox>
{
#region DependencyProperties
public static readonly DependencyProperty RegularExpressionProperty = DependencyProperty.Register(
nameof(RegularExpression),
typeof(string),
typeof(TextBoxInputBehavior),
new FrameworkPropertyMetadata(".*"));
public string RegularExpression
{
get
{
if (IsInteger)
return #"^[0-9\-]+$";
if (IsNumeric)
return #"^[0-9.\-]+$";
return (string)GetValue(RegularExpressionProperty);
}
set { SetValue(RegularExpressionProperty, value); }
}
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register(
nameof(MaxLength),
typeof(int),
typeof(TextBoxInputBehavior),
new FrameworkPropertyMetadata(int.MinValue));
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
nameof(EmptyValue),
typeof(string),
typeof(TextBoxInputBehavior));
public string EmptyValue
{
get { return (string)GetValue(EmptyValueProperty); }
set { SetValue(EmptyValueProperty, value); }
}
public static readonly DependencyProperty IsNumericProperty = DependencyProperty.Register(
nameof(IsNumeric),
typeof(bool),
typeof(TextBoxInputBehavior));
public bool IsNumeric
{
get { return (bool)GetValue(IsNumericProperty); }
set { SetValue(IsNumericProperty, value); }
}
public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
nameof(IsInteger),
typeof(bool),
typeof(TextBoxInputBehavior));
public bool IsInteger
{
get { return (bool)GetValue(IsIntegerProperty); }
set
{
if (value)
SetValue(IsNumericProperty, true);
SetValue(IsIntegerProperty, value);
}
}
public static readonly DependencyProperty AllowSpaceProperty = DependencyProperty.Register(
nameof(AllowSpace),
typeof (bool),
typeof (TextBoxInputBehavior));
public bool AllowSpace
{
get { return (bool) GetValue(AllowSpaceProperty); }
set { SetValue(AllowSpaceProperty, value); }
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject == null)
return;
AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
}
private void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
string text;
if (AssociatedObject.Text.Length < AssociatedObject.CaretIndex)
text = AssociatedObject.Text;
else
text = TreatSelectedText(out var remainingTextAfterRemoveSelection)
? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
: AssociatedObject.Text.Insert(AssociatedObject.CaretIndex, e.Text);
e.Handled = !ValidateText(text);
}
private void PreviewKeyDownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
e.Handled = !AllowSpace;
if (string.IsNullOrEmpty(EmptyValue))
return;
string text = null;
// Handle the Backspace key
if (e.Key == Key.Back)
{
if (!TreatSelectedText(out text))
{
if (AssociatedObject.SelectionStart > 0)
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
}
}
// Handle the Delete key
else if (e.Key == Key.Delete)
{
// If text was selected, delete it
if (!TreatSelectedText(out text) && AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
{
// Otherwise delete next symbol
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
}
}
if (text == string.Empty)
{
AssociatedObject.Text = EmptyValue;
if (e.Key == Key.Back)
AssociatedObject.SelectionStart++;
e.Handled = true;
}
}
private void PastingHandler(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(DataFormats.Text))
{
var text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
if (!ValidateText(text))
e.CancelCommand();
}
else
e.CancelCommand();
}
public bool ValidateText(string text)
{
return new Regex(RegularExpression, RegexOptions.IgnoreCase).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength);
}
/// <summary>
/// Handle text selection.
/// </summary>
/// <returns>true if the character was successfully removed; otherwise, false.</returns>
private bool TreatSelectedText(out string text)
{
text = null;
if (AssociatedObject.SelectionLength <= 0)
return false;
var length = AssociatedObject.Text.Length;
if (AssociatedObject.SelectionStart >= length)
return true;
if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
return true;
}
}
All the good credit for above Behaviour class goes to blindmeis, I merely tweaked it over time. After checking his Blog I see he has a newer version of it so you may check it out. I was very happy to find out I could use his behaviour on DataGrid as well!
This solution worked really well, you can edit the cell properly via mouse/keyboard, paste the contents properly, use any binding source update triggers, use any string formatting etc. - it just works.
Here's an example of how to use it:
<local:DataGridNumberColumn Header="Nullable Int Currency" IsInteger="True" Binding="{Binding IntegerNullable, TargetNullValue=''}" StringFormat="{}{0:C}" />
Hope this helps someone.
I went on from Omris approach
however i wanted to be able to delete the cell value after it has input in case they wanted to clear it
The way i did this was overriding the CommitCellEdit method and making the string null instead of blank. Im also using decimal? in my case
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(System.Windows.FrameworkElement editingElement, System.Windows.RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
protected override bool CommitCellEdit(System.Windows.FrameworkElement editingElement)
{
TextBox tb = editingElement as TextBox;
if (string.IsNullOrEmpty(tb.Text))
tb.Text = null;
return base.CommitCellEdit(editingElement);
}
void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
try
{
Convert.ToDecimal(e.Text);
}
catch
{
// Show some kind of error message if you want
// Set handled to true
e.Handled = true;
}
}
}

Module not rendering

In my application i'm trying to load the main module trough code. Everything works up to the point of rendering and i have NO clue why it isn't rendering. The content has the right values and everything.. My guess is that something went wibbly wobbly and I seem to be missing it.
Control that holds the view
[ContentPropertyAttribute("ContentView")]
public class ContentControlExtened : ContentControl
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(
"Type",
typeof(Type),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentTypeChanged));
public static readonly DependencyProperty ContentViewProperty =
DependencyProperty.Register(
"View",
typeof(FrameworkElement),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentViewChanged));
private static void ContentViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
public ContentControlExtened()
{
this.Loaded += ContentControlLoaded;
}
private void ContentControlLoaded(object sender, RoutedEventArgs e)
{
this.LoadContent();
}
private void LoadContent()
{
UserControl u = null;
if (Type != null)
{
u = (UserControl)ServiceLocator.Current.GetInstance(this.Type);
}
u.Background = new SolidColorBrush(Color.FromRgb(0, 0, 255));
this.View = u;
}
public Type Type
{
get { return (Type)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
}
Method in shell to load the main view of the given moduleInfo
private void OpenMainView(ModuleInfo module)
{
Type moduleType = Type.GetType(module.ModuleType);
var moduleMainViewType = moduleType.Assembly.GetType(moduleType.Namespace + ".Views.MainView");
this.ContentHolder.MainContent.Type = moduleMainViewType;
}
The contructor of the mainview is straight forward. Just standard control InitializeComponent() and Datacontext is being set trough the servicelocator.
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
Here you are getting and setting the ContentProperty. It should be ContentViewProperty.

Categories