Set maximum number of characters for MahApps NumericUpDown control - wpf

I am using the NumericUpDown control from MahApps, because of its +/- buttons and the ability to set the maximum/minimum allowed value. I now need to somehow limit the number of character, that can be input to the box.
For a standard TextBox this would be done with the MaxLength property, but this property does not exist for the NumericUpDown control.
Am I missing something? Is there some other way to achieve this?

No, there is no such property, but you can easily extend NumericUpDown and add it. BTW, when you focus out, it checks the Maximum allowed value and the input value.
C#
using MahApps.Metro.Controls;
using System.Windows;
using System.Windows.Input;
namespace TestApp.Controls
{
class ExtendedNumericUpDown : NumericUpDown
{
public int MaxLenght
{
get { return (int)GetValue(MaxLenghtProperty); }
set { SetValue(MaxLenghtProperty, value); }
}
public static readonly DependencyProperty MaxLenghtProperty =
DependencyProperty.Register(nameof(MaxLenght), typeof(int), typeof(ExtendedNumericUpDown), new PropertyMetadata(10));
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
e.Handled = ((System.Windows.Controls.TextBox)e.OriginalSource).Text.Length >= MaxLenght;
base.OnPreviewTextInput(e);
}
}
}
XAML
<ctrl:ExtendedNumericUpDown Minimum="0" Maximum="100" MaxLenght="3"/>

Building on #Alex's answer, you can take it one step further by creating a behavior. The behavior can be used by many different types of controls and you don't need to subclass NumericUpDown
public class MaxCharactersBehavior : Behavior<UIElement>
{
public int MaxCharacters { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += AssociatedObject_PreviewTextInput;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewTextInput -= AssociatedObject_PreviewTextInput;
}
private void AssociatedObject_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = ((System.Windows.Controls.TextBox)e.OriginalSource).Text.Length >= MaxCharacters;
}
}
<mah:NumericUpDown Width="150" Maximum="999" Minimum="0">
<i:Interaction.Behaviors>
<behaviors:MaxCharactersBehavior MaxCharacters="3" />
</i:Interaction.Behaviors>
</mah:NumericUpDown>

The NumericUpDown control accepts Maximum and Minimum arguments,
<Controls:NumericUpDown Minimum="0" Maximum="{Binding TotalPages}"/>
If you can't find them, try to update the NuGet package of MahApps.

Related

How to set focus on AND SELECT ALL of an initial text box (MVVM-Style)?

I have a simple WPF page with one text box field that my client wants highlighted when the page shows up. In code behind, it would be three lines, but I'm sogging through MVVM (which I'm starting to think is a little over-rated). I've tried so many different variants of behaviors and global events and FocusManager.FocusedElement, but nothing I do will do this.
Ultimately the most of the code I've been using calls these two lines:
Keyboard.Focus(textBox);
textBox.SelectAll();
But no matter where I put these lines the text box is only focused; no text is selected. I have never had this much trouble with something so simple. I've been hitting my head against the internets for two hours. Does anyone know how to do this?
Again, all I want to do is have the text box focus and it's text all selected when the page is navigated to. Please help!
"Focus" and "Select All Text from a TextBox" is a View-specific concern.
Put that in code Behind. It does not break the MVVM separation at all.
public void WhateverControl_Loaded(stuff)
{
Keyboard.Focus(textBox);
textBox.SelectAll();
}
If you need to do it in response to a specific application/business logic. Create an Attached Property.
Or:
have your View resolve the ViewModel by:
this.DataContext as MyViewModel;
then create some event in the ViewModel to which you can hook:
public class MyViewModel
{
public Action INeedToFocusStuff {get;set;}
public void SomeLogic()
{
if (SomeCondition)
INeedToFocusStuff();
}
}
then hook it up in the View:
public void Window_Loaded(Or whatever)
{
var vm = this.DataContext as MyViewModel;
vm.INeedToFocusStuff += FocusMyStuff;
}
public void FocusMyStuff()
{
WhateverTextBox.Focus();
}
See how this simple abstraction keeps View related stuff in the View and ViewModel related stuff in the ViewModel, while allowing them to interact. Keep it Simple. You don't need NASA's servers for a WPF app.
And no MVVM is not overrated, MVVM is extremely helpful and I would say even necessary. You'll quickly realize this as soon as you begin working with ItemsControls such as ListBoxes or DataGrids.
Here are some workthroughs:
Use Interaction.Behaviors
You can install the NuGet package named Microsoft.Xaml.Behaviors.Wpf, and write your own Behavior:
using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;
public class AutoSelectAllBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.GotFocus += AssociatedObject_GotFocus;
}
private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{
if (AssociatedObject is TextBox box)
box.SelectAll();
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
}
}
and attach this behavior to the TextBox in the xaml:
<!-- xmlns:i="http://schemas.microsoft.com/xaml/behaviors" -->
<TextBox>
<i:Interaction.Behaviors>
<br:AutoSelectAllBehavior />
</i:Interaction.Behaviors>
</TextBox>
Use Interaction.Triggers
This is in the same package as mentioned in the last section. This special can be considered to let you be able to bind UIElement events to your ViewModel.
In your ViewModel, suppose you have an ICommand relay command (You may also need Microsoft.Toolkit.MVVM so that you can use some handy relay commands):
public ICommand SelectAllCommand { get; }
public ViewModel()
{
SelectAllCommand = new RelayCommand<TextBox>(box => box.SelectAll());
}
and then attach this command to the TextBox by setting the triggers:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding SelectAllCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TextBox}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Use Attached Property
You can also use attached property (write your own class derived from TextBox and use dependency property is quite similar):
using System.Windows;
using System.Windows.Controls;
public class TextBoxProperties
{
public static bool GetAutoSelectAll(DependencyObject obj)
{
return (bool)obj.GetValue(AutoSelectAllProperty);
}
public static void SetAutoSelectAll(DependencyObject obj, bool value)
{
obj.SetValue(AutoSelectAllProperty, value);
}
public static readonly DependencyProperty AutoSelectAllProperty =
DependencyProperty.RegisterAttached("AutoSelectAll", typeof(bool), typeof(TextBoxProperties), new PropertyMetadata(false, TextBoxProperties_PropertyChanged));
private static void TextBoxProperties_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
if (d is TextBox box)
{
box.GotFocus += TextBox_GotFocus;
}
}
}
private static void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var box = sender as TextBox;
box.SelectAll();
}
}
Then you can use it like:
<!-- xmlns:ap="..." -->
<TextBox ap:TextBoxProperties.AutoSelectAll="True" />

Hook up right click events to all textbox in silverlight

Is there anyway to add right click events to all textbox controls in silverlight without needing to manually adding it to each control in the whole project?
doing like:
<TextBox x:Name="txtName" MouseRightButtonUp="txtName_MouseRightButtonUp"
MouseRightButtonDown="txtName_MouseRightButtonDown" /></TextBox>
then fixing the events in the .cs for about 50+ (hopefully it's just 50+) textboxes can take a while.
If not then what might be the easiest way to do this?
You can extend your textbox
class SimpleTextBox
{
public SimpleTextBox()
{
DefaultStyleKey = typeof (SimpleCombo);
MouseRightButtonDown += OnMouseRightButtonDown;
}
private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs
mouseButtonEventArgs)
{
//TODO something
}
}
==========
And use this control.
Or as alternative solution - you can create behavior:
CS:
...
using System.Windows.Interactivity;
public class TextBoxBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseRightButtonDown += AssociatedObject_MouseRightButtonDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.MouseRightButtonDown -= AssociatedObject_MouseRightButtonDown;
}
private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
e.Handled = true;
// DO SOMETHING
}
}
XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<TextBox ...>
<i:Interaction.Behaviors>
<local:TextBoxBehavior />
</i:Interaction.Behaviors>
</TextBox>
And attach this handler to your TextBox general style.
My answer to this question is also the answer to your question.
In short it's probably easiest to derive a type from TextBox, put your MouseRightButtonDown event handler in there and replace all existing instances of textBox with your type.

Multiline Textbox with automatic vertical scroll

There are a lot of similiar questions over internet, on SO included, but proposed solutions doesn't work in my case.
Scenario : there is a log textbox in xaml
<TextBox Name="Status"
Margin="5"
Grid.Column="1"
Grid.Row="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="600"
Height="310"/>
There are methods in code-behind that do some work and add some multiline (maybe that's the problem?) messages into this textbox:
private static void DoSomeThings(TextBox textBox)
{
// do work
textBox.AppendText("Work finished\r\n"); // better way than Text += according to msdn
// do more
textBox.AppendText("One more message\r\n");
...
}
private static void DoSomething2(TextBox textBox)
{
// same as first method
}
Need to scroll to bottom of textbox after all actions take place. Tried ScrollToEnd(), ScrollToLine, wrapping textbox into ScrollViewer, Selection and Caret workarounds, attaching ScrollToEnd to TextChanged. None of this works, after execution lines that overflow textbox height still need to be scrolled to manually. Sorry for duplicate question, i guess i'm missing some minor issues that can be resolved quickly by someone that has fresh vision on the problem.
According to this question: TextBox.ScrollToEnd doesn't work when the TextBox is in a non-active tab
You have to focus the text box, update the caret position and then scroll to end:
Status.Focus();
Status.CaretIndex = Status.Text.Length;
Status.ScrollToEnd();
EDIT
Example TextBox:
<TextBox TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
AcceptsReturn="True" Name="textBox"/>
If you make it into a simple custom control then you don't need any code behind to do the scrolling.
public class ScrollingTextBox : TextBox {
protected override void OnInitialized (EventArgs e) {
base.OnInitialized(e);
VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
}
protected override void OnTextChanged (TextChangedEventArgs e) {
base.OnTextChanged(e);
CaretIndex = Text.Length;
ScrollToEnd();
}
}
If you're using WPF it would be far better to use binding rather than passing the text box around in the code behind.
If you don't like code behind to much, here is an AttachedProperty that will do the trick :
namespace YourProject.YourAttachedProperties
{
public class TextBoxAttachedProperties
{
public static bool GetAutoScrollToEnd(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToEndProperty);
}
public static void SetAutoScrollToEnd(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToEndProperty, value);
}
// Using a DependencyProperty as the backing store for AutoScrollToEnd. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoScrollToEndProperty =
DependencyProperty.RegisterAttached("AutoScrollToEnd", typeof(bool), typeof(TextBoxAttachedProperties), new PropertyMetadata(false, AutoScrollToEndPropertyChanged));
private static void AutoScrollToEndPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is TextBox textbox && e.NewValue is bool mustAutoScroll && mustAutoScroll)
{
textbox.TextChanged += (s, ee)=> AutoScrollToEnd(s, ee, textbox);
}
}
private static void AutoScrollToEnd(object sender, TextChangedEventArgs e, TextBox textbox)
{
textbox.ScrollToEnd();
}
}
}
And then in your xaml just do :
<TextBox
AcceptsReturn="True"
myAttachedProperties:TextBoxAttachedProperties.AutoScrollToEnd="True"/>
Just don't forget to add at the top of your xaml file
xmlns:myAttachedProperties="clr-namespace:YourProject.YourAttachedProperties"
And voila
Thanks! I have added this to remember the original focus:
var oldFocusedElement = FocusManager.GetFocusedElement(this);
this.textBox.Focus();
this.textBox.CaretIndex = this.textBox.Text.Length;
this.textBox.ScrollToEnd();
FocusManager.SetFocusedElement(this, oldFocusedElement);

ScrollViewer and ScrollToVerticalOffset with mvvm

I would like to use the ScrollToVerticalOffset method of a ScrollViewer to go to the top of the scrollviewer.
But with a MVVM approch.
I think I have to create a dependency property to take this behavior.
EDIT :
The behavior is :
public class ScrollPositionBehavior : Behavior<FrameworkElement>
{
public double Position
{
get { return (double)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(double), typeof(ScrollPositionBehavior), new PropertyMetadata((double)0, new PropertyChangedCallback(OnPositionChanged)));
protected override void OnAttached()
{
base.OnAttached();
}
private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollPositionBehavior behavior = d as ScrollPositionBehavior;
double value = (double)e.NewValue;
((ScrollViewer)(behavior.AssociatedObject)).ScrollToVerticalOffset(value);
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
used like :
<ScrollViewer>
<Interactivity:Interaction.Behaviors>
<fxBehavior:ScrollPositionBehavior Position="{Binding Position}" />
</Interactivity:Interaction.Behaviors>
<other things ...>
</ScrollViewer>
with
xmlns:Interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:fxBehavior="clr-namespace:MyNamespace.Behavior;assembly=MyAssembly"
I have a parser xaml exception :
this is a : AG_E_PARSER_BAD_PROPERTY_VALUE
Note that i'm using the behavior based on a FrameworkElement, as I'm using silverlight 3 (in fact, this is SL for WP7). I've seen that the binding should work only for FrameworkElement.
Thanks in advance for your help
You were on the right way. First of all you need to change your OnPositionChanged method to find out which instance of the behavior had its Position changed:
private static void OnPositionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ScrollPositionBehavior behavior = d as ScrollPositionBehavior;
double value = (double)e.NewValue;
behavior.AssociatedObject.ScrollToVerticalOffset(value);
}
Then, you'll get the ScrollViewer as associated object when you attach the behavior to it:
<ScrollViewer>
<i:Interaction.Behaviors>
<my:ScrollPositionBehavior Position="{what you need, e.g. Binding}" />
</i:Interaction.Behaviors>
</ScrollViewer>
Note that if you use a Binding it can be a OneWay binding, because the Position will never get updated by the behavior itself.

Workaround for UpdateSourceTrigger LostFocus on Silverlight Datagrid?

I have a Silverlight 2 application that validates data OnTabSelectionChanged. Immediately I began wishing that UpdateSourceTrigger allowed more than just LostFocus because if you click the tab without tabbing off of a control the LINQ object is not updated before validation.
I worked around the issue for TextBoxes by setting focus to another control and then back OnTextChanged:
Private Sub OnTextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
txtSetFocus.Focus()
sender.Focus()
End Sub
Now I am trying to accomplish the same sort of hack within a DataGrid. My DataGrid uses DataTemplates generated at runtime for the CellTemplate and CellEditingTemplate. I tried writing the TextChanged="OnTextChanged" into the TextBox in the DataTemplate, but it is not triggered.
Anyone have any ideas?
You can do it with a behavior applied to the textbox too
// xmlns:int is System.Windows.Interactivity from System.Windows.Interactivity.DLL)
// xmlns:behavior is your namespace for the class below
<TextBox Text="{Binding Description,Mode=TwoWay,UpdateSourceTrigger=Explicit}">
<int:Interaction.Behaviors>
<behavior:TextBoxUpdatesTextBindingOnPropertyChanged />
</int:Interaction.Behaviors>
</TextBox>
public class TextBoxUpdatesTextBindingOnPropertyChanged : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += new TextChangedEventHandler(TextBox_TextChanged);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= TextBox_TextChanged;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
}
}
This blog post shows how to update the source of a textbox explicitly using attached property:
http://www.thomasclaudiushuber.com/blog/2009/07/17/here-it-is-the-updatesourcetrigger-for-propertychanged-in-silverlight/
You could easily modify it to work with other controls as well...
I ran into this same problem using MVVM and Silverlight 4. The problem is that the binding does not update the source until after the textbox looses focus, but setting focus on another control doesn't do the trick.
I found a solution using a combination of two different blog posts. I used the code from Patrick Cauldwell's DefaultButtonHub concept, with one "SmallWorkaround" from SmallWorkarounds.net
http://www.cauldwell.net/patrick/blog/DefaultButtonSemanticsInSilverlightRevisited.aspx
www.smallworkarounds.net/2010/02/elementbindingbinding-modes.html
My change resulted in the following code for the DefaultButtonHub class:
public class DefaultButtonHub
{
ButtonAutomationPeer peer = null;
private void Attach(DependencyObject source)
{
if (source is Button)
{
peer = new ButtonAutomationPeer(source as Button);
}
else if (source is TextBox)
{
TextBox tb = source as TextBox;
tb.KeyUp += OnKeyUp;
}
else if (source is PasswordBox)
{
PasswordBox pb = source as PasswordBox;
pb.KeyUp += OnKeyUp;
}
}
private void OnKeyUp(object sender, KeyEventArgs arg)
{
if (arg.Key == Key.Enter)
if (peer != null)
{
if (sender is TextBox)
{
TextBox t = (TextBox)sender;
BindingExpression expression = t.GetBindingExpression(TextBox.TextProperty);
expression.UpdateSource();
}
((IInvokeProvider)peer).Invoke();
}
}
public static DefaultButtonHub GetDefaultHub(DependencyObject obj)
{
return (DefaultButtonHub)obj.GetValue(DefaultHubProperty);
}
public static void SetDefaultHub(DependencyObject obj, DefaultButtonHub value)
{
obj.SetValue(DefaultHubProperty, value);
}
// Using a DependencyProperty as the backing store for DefaultHub. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DefaultHubProperty =
DependencyProperty.RegisterAttached("DefaultHub", typeof(DefaultButtonHub), typeof(DefaultButtonHub), new PropertyMetadata(OnHubAttach));
private static void OnHubAttach(DependencyObject source, DependencyPropertyChangedEventArgs prop)
{
DefaultButtonHub hub = prop.NewValue as DefaultButtonHub;
hub.Attach(source);
}
}
This should be included in some sort of documentation for Silverlight :)
I know it's old news... but I got around this by doing this:
Text="{Binding Path=newQuantity, UpdateSourceTrigger=PropertyChanged}"

Resources