I have an attached property to textboxes in my view. The attached property performs validation on the textbox input and performs other chores. The attached property validation routine raises an event which is being watched by the viewmodel.
- Does this "violate" MVVM reasoning by having the viewmodel obtain the invalid TextBoxes?
- How will the GC deal with the static events from the attached property when the usercontrol containing the textboxes is removed?
- If specific code is needed to avoid memory leaks, how is that done?
- Is there a preferred way to do this?
Sorry for the long list, but Google does not address this situation.
Any and all help is appreciated. Thank you for your consideration.
(VS2010 .net 4.5)
TIA
ViewModel
class CheckInViewModel : SimpleViewModelBase
{
public CheckInViewModel()
{
InValidTextBoxes = new List<TextBox>();
Stargate_V.Helpers.ColorMaskingTextBoxBehavior.Validated += (sender, e) =>
{
if (e.valid)
InValidTextBoxes.Remove(e.sender);
else
InValidTextBoxes.Add(e.sender);
};
}
List<TextBox> InValidTextBoxes;
}
XAML
<TextBox
h:ColorMaskingTextBoxBehavior.Mask="^[MmFf]$"
Text="{Binding Sex}"
Height="24" HorizontalAlignment="Right" Margin="0,55,665,0" VerticalAlignment ="Top" Width="36" />
Attached Properity
public class ColorMaskingTextBoxBehavior : DependencyObject
{
// Entrance point from Xaml
public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
typeof(string),
typeof(ColorMaskingTextBoxBehavior),
new FrameworkPropertyMetadata(OnMaskChanged));
...........................
// Callback from XAML initialization of the attached property.
private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var textBox = dependencyObject as TextBox;
var mask = e.NewValue as string;
textBox.PreviewTextInput -= textBox_PreviewTextInput;
textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
DataObject.RemovePastingHandler(textBox, Pasting);
DataObject.RemoveCopyingHandler(textBox, NoDragCopy);
CommandManager.RemovePreviewExecutedHandler(textBox, NoCutting);
if (mask == null)
{
textBox.ClearValue(MaskProperty);
textBox.ClearValue(MaskExpressionProperty);
}
else
{
textBox.SetValue(MaskProperty, mask);
SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));
textBox.PreviewTextInput += textBox_PreviewTextInput;
textBox.PreviewKeyDown += textBox_PreviewKeyDown;
DataObject.AddPastingHandler(textBox, Pasting);
DataObject.AddCopyingHandler(textBox, NoDragCopy);
CommandManager.AddPreviewExecutedHandler(textBox, NoCutting);
}
}
private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
var textBox = sender as TextBox;
var maskExpression = GetMaskExpression(textBox);
string passHex = (string)textBox.GetValue(PassColorProperty);
string failHex = (string)textBox.GetValue(FailColorProperty);
Color passColor = Extensions.ToColorFromHex(passHex);
Color failColor = Extensions.ToColorFromHex(failHex);
if (maskExpression == null)
{
return;
}
var proposedText = GetProposedText(textBox, e.Text);
if (!maskExpression.IsMatch(proposedText))
{
textBox.Background = new SolidColorBrush(failColor);
ValidationEventArgs args = new ValidationEventArgs();
args.sender = textBox;
args.valid = false;
OnValidation(args);
}
else
{
textBox.Background = new SolidColorBrush(passColor);
ValidationEventArgs args = new ValidationEventArgs();
args.sender = textBox;
args.valid = true;
OnValidation(args);
}
}
Event Called from the above code
public static event EventHandler<ValidationEventArgs> Validated;
static void OnValidation(ValidationEventArgs e)
{
EventHandler<ValidationEventArgs> handler = Validated;
if (handler != null)
{
handler(null, e);
}
}
public class ValidationEventArgs : EventArgs
{
public TextBox sender;
public bool valid;
}
Yes, I would argue that this violates MVVM. Your view model should have no knowledge of the views whatsoever. The question to always ask yourself is "can I run my application without creating any views?". In this case your view model is interacting directly with a list of TextBoxes, so the pattern is broken.
There are several ways of achieving your goal here, probably the most simple is to create a handler in your view model that gets called when your TextBox text changes:
Now all you need is a behavior with an attached property that you can bind to this handler:
And then finally add the behaviour to the TextBox in question and bind the handler:
EDIT: If you don't want to use a Blend behaviour then you can also do it with an attached behaviour:
And the corresponding XAML: