There is a very similar question already posted. In fact, the result of the answer in that post is exactly what I'm after, but I have no codebehind to place that code in. All of our logic is encapsulated in a ViewModel. Since the ViewModel is not supposed to have direct references to specific visual elements, this code cannot exist there either. Is there a way to perform this same thing in XAML somehow, or have I finally ran into a reason to be forced to create codebehind files?
You could try doing something with attached properties..it's a bit elaborate, but it does the same as the other answer, so i think it should work:
public class DependencyPropertyCollection : List<DependencyProperty>
{ }
public static class ValidationUtil
{
public static readonly DependencyProperty ForceValidationProperty =
DependencyProperty.RegisterAttached("ForceValidation", typeof(DependencyPropertyCollection), typeof(ValidationUtil), new PropertyMetadata(OnForceValidationChanged));
private static void OnForceValidationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded += OnElementLoaded;
}
private static void OnElementLoaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= OnElementLoaded;
foreach (DependencyProperty property in GetForceValidation(element))
element.GetBindingExpression(property).UpdateSource();
}
public static DependencyPropertyCollection GetForceValidation(DependencyObject obj)
{
return (DependencyPropertyCollection)obj.GetValue(ForceValidationProperty);
}
public static void SetForceValidation(DependencyObject obj, DependencyPropertyCollection value)
{
obj.SetValue(ForceValidationProperty, value);
}
}
And you use it like this:
<TextBlock Text="{Binding Text}">
<local:ValidationUtil.ForceValidation>
<local:DependencyPropertyCollection>
<x:StaticExtension Member="TextBlock.TextProperty"/>
</local:DependencyPropertyCollection>
</local:ValidationUtil.ForceValidation>
</TextBlock>
Inside the collection you specify each DependencyProperty which has a binding that you want to validate.
Related
I'm working with a custom WPF behavior (the one from System.Windows.Interactivity) showing a couple of dependency properties, one of those being a string. The behavior also overrides OnAttached in order to grab a reference to its AssociatedObject UI control.
When that attached property is data-bound to viewModel and is later changed (and notified) at some point, everything seems fine: OnAttached has been fired "at the beginning", and later the PropertyChangedCallback gets fired.
The issue I see is when the property is not bound, but set to a "static" value in XAML. In this case the PropertyChangedCallback gets fired before OnAttached, when the behavior has yet to know its associated UI control and basically cannot do anything in reaction to that property changing.
I guess I'm missing something on how things should be done in this case. Any help in understanding this is appreciated. TA
EDIT
Showing here some code, if that might be helpful in this case:
public class SomeUIControlBehaviour : Behavior<SomeUIControl>
{
protected override void OnAttached()
{
base.OnAttached();
_attachedUIControl = this.AssociatedObject;
}
protected override void OnDetaching()
{
base.OnDetaching();
_attachedUIControl = null;
}
private SomeUIControl _attachedUIControl;
private void MessageChanged()
{
if (_attachedUIControl != null)
{
// do something on it
}
else
{
// bummer!
}
}
// Text property + dependency property
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
private static string _defaultMessage = String.Empty;
// Using a DependencyProperty as the backing store for Message. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(SomeUIControlBehaviour),
new PropertyMetadata(_defaultMessage, MessagePropertyChanged));
private static void MessagePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs evt)
{
//Debug.WriteLine("MessagePropertyChanged, on " + sender.GetType().Name + ", to value " + evt.NewValue);
SomeUIControlBehaviour behaviour = sender as SomeUIControlBehaviour;
if (behaviour == null)
{
Debug.Fail("Message property should be used only with SomeUIControlBehaviour");
return;
}
behaviour.MessageChanged();
}
}
As per comment, one simple answer could be:
when behavior gets attached, just check if the property has already a value (maybe different than default) and in that case do what the PropertyChangedCallback was supposed to do.
When the user presses the Insert key in a WPF TextBox, the control toggles between insert and overwrite mode. Usually, this is visualised by using a different cursor (line vs. block) but that's not the case here. Since there is absolutely no way for the user to know that overwrite mode is active, I'd simply like to disable it completely. When the user presses the Insert key (or however that mode could possibly be activated, intentionally or accidently), the TextBox should simply stay in insert mode.
I could add some key press event handler and ignore all such events, pressing the Insert key with no modifiers. Would that be enough? Do you know a better alternative? There's a number of TextBox controls throughout my views, and I don't want to add event handlers everywhere...
You could make an AttachedProperty and use the method ChrisF suggested, this way its eay to add to the TextBoxes you want thoughout your application
Xaml:
<TextBox Name="textbox1" local:Extensions.DisableInsert="True" />
AttachedProperty:
public static class Extensions
{
public static bool GetDisableInsert(DependencyObject obj)
{
return (bool)obj.GetValue(DisableInsertProperty);
}
public static void SetDisableInsert(DependencyObject obj, bool value)
{
obj.SetValue(DisableInsertProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisableInsertProperty =
DependencyProperty.RegisterAttached("DisableInsert", typeof(bool), typeof(Extensions), new PropertyMetadata(false, OnDisableInsertChanged));
private static void OnDisableInsertChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox && e != null)
{
if ((bool)e.NewValue)
{
(d as TextBox).PreviewKeyDown += TextBox_PreviewKeyDown;
}
else
{
(d as TextBox).PreviewKeyDown -= TextBox_PreviewKeyDown;
}
}
}
static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Insert && e.KeyboardDevice.Modifiers == ModifierKeys.None)
{
e.Handled = true;
}
}
To avoid adding handlers everywhere you could subclass the TextBox and add a PreviewKeyDown event handler which does as you suggest.
In the constructor:
public MyTextBox()
{
this.KeyDown += PreviewKeyDownHandler;
}
private void PreviewKeyDownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Insert)
{
e.Handled = true;
}
}
However, this does mean that you will need to replace all usages of TextBox with MyTextBox in your XAML, so unfortunately you are going to have to edit all your views anyway.
I'm trying to implement a ConnectionString dialog, where the user can enter all the values which are necessary to create a valid ConnectionString, e.g. UserID, IntegratedSecurity, etc....
There is also a ComboBox which enlists all the available databases which can be found at this endpoint. This ComboBox should only bind to the ItemsSource, when it is opened and not when the user changes for example the UserID.
Is there a easy way to refresh the ItemsSource values only when the Values are displayed(for example when opening the combobox). The Problem is when the user enters invalid values there is always an exception because the user has not finished entering all the necessary values.
I have already tried to implement this with the event ComboBox_DropDownOpened but I wonder if there is a more practical way to achieve this. I have noticed there is a BindingProperty "UpdateSourceTrigger" but I don't know if I can use it for my problem.
Thanks for any help!
<ComboBox Text="{Binding InitialCatalog}"
SelectedValue="{Binding InitialCatalog}"
ItemsSource="{Binding Databases}" IsEditable="True"/>
If the event ComboBox_DropDownOpened is working you can wrap it in a behavior which should look like this :
internal class ItemsSourceBindingOnOpenBehavior
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached("Source", typeof(ObservableCollection<string>),
typeof(ItemsSourceBindingOnOpenBehavior),
new UIPropertyMetadata(null, OnSourceChanged));
public static ObservableCollection<string> GetSource(DependencyObject obj)
{
return (ObservableCollection<string>)obj.GetValue(SourceProperty);
}
public static void SetSource(DependencyObject obj, ObservableCollection<string> value)
{
obj.SetValue(SourceProperty, value);
}
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SetSource(d);
}
private static void SetSource(DependencyObject d)
{
var cbo = d as ComboBox;
if (cbo != null) cbo.DropDownOpened += (s, a) => { cbo.ItemsSource = GetSource(cbo); };
}
}
To activate the behavior use the two provided attached properties in your XAML :
<ComboBox a:ItemsSourceBindingOnOpenBehavior.Source="{Binding ViewModelCollection}"/>
Here I have a simple Blend Behavior. It has a single DependencyProperty which I want to do something with during initialization in OnAttached. But I can't because the behavior appears to be attached before it is even initialized!
Usage:
<Button>
<e:Interaction.Behaviors>
<local:TestBehavior Test1="{Binding ValueOnViewModel}"/>
</e:Interaction.Behaviors>
<Button>
Definition:
class TestBehavior : Behavior<FrameworkObject>
{
public static readonly DependencyProperty Test1Property
= DependencyProperty.Register("Test1", typeof(int), typeof(TestBehavior),
new PropertyMetadata(OnTest1Property_Changed));
private static void OnTest1Property_Changed(DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
// This gets called _after_ OnAttached!
}
public int Test1
{
get { return (int)GetValue(Test1Property); }
set { SetValue(Test1Property, value); }
}
protected override void OnAttached()
{
base.OnAttached();
// No matter what Test1Property is bound to in XAML 'a' will be default
// value because as this point the Behavior is attached, but not
// initialized!
int a = Test1;
}
}
This strikes me as really bizarre. In this simple case I can get around the issue by performing my initialization in OnTest1Property_Changed instead of in OnAttached, (albeit with the mild inconvenience of being in a static context rather than an instance context).
However, what if I have a rather less trivial Behavior which has multiple properties? In some usages of the behavior all the DP's might be explicitly set, whereas in other cases only some of the DP's might be set while using the default values of the DP's that are not explicitly set. I can handle the change event for all of the DP's the behavior defines, but I have no way of knowing which DP's the client has set explicitly in XAML, so I have no way of knowing with change notifications have to have occurred before initialization is complete.
So how in that non-trivial case can I possibly know when initialization of the behavior is complete? This seems like such a glaring weakness that I can only assume I've missed something obvious.
[Update]
Using Sorskoot's suggestion I knocked up this simple base class which I can use as the base for my Behaviors instead.
public class BehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
protected virtual void OnLoaded()
{
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
OnLoaded();
}
}
I would suggest attaching to the Loaded event during in the OnAttached method and do you initialization in there.
protected override void OnAttached()
{
base.OnAttached();
base.AssociatedObject.Loaded += onloaded;
}
private void onloaded(object sender, RoutedEventArgs e)
{
int a = Test1;
// Test1 should contain a value here.
}
I got a slider that on value change forces a fairly serious computation, so I want to throttle it to fire actual event after for example 50ms pass when user has finished sliding it.
While I learned some various stuff about Rx its unclear how should I approach this using MVVM pattern.
In my current MVVM approach I got slider value bound to my viewModel. I would prefer to add Rx throttle with minimal possible impact on existing code (as a beginning at least).
Ive seen some other threads about MVVM and Rx and I don't think they lead me to some exact direction with my problem. I see various possible approaches and would like not to invent a bycicle.
In this case, you should bind to the PropertyChanged event of your ViewModel, something like:
Observable.FromEvent<PropertyChangedEventArgs>(x => this.PropertyChanged +=x, x => this.PropertyChanged -= x)
.Where(x => x.PropertyName == "SliderName")
.Select(_ => this.SliderName)
.Throttle(TimeSpan.FromMilliseconds(50));
Or, if you were using ReactiveUI, it'd look like this:
this.WhenAnyValue(x => x.SliderName)
.Throttle(TimeSpan.FromMilliseconds(50), RxApp.DeferredScheduler);
Lets just outline the problem. You have a View Model which has some double typed Property. When a value is assigned to this property a fairly expensive calculation takes place. Wouldn't normally be a problem but when the UI binds the value of a Slider to this property the rapid changes generated does create a problem.
First decision to be made is between the view and view-model which is responsible for dealing with this problem. It could be argued both ways the View-Model has "chosen" to make a property assignment an expensice operatione on the other hand the View has "chosen" to assign the property using a Slider.
My choice would be on view side of things because thats a better place to implement this. However rather than fiddle with the View directly I would build a new Control to add the feature. Let's call it the DelaySlider. It will derive from Silder and have two additional dependency properties Delay and DelayedValue. The DelayedValue will match the existing value of Value property but only after Delay milliseconds have elapsed since the last Value changed.
Here is the full code for the control:-
public class DelaySlider : Slider
{
private DispatcherTimer myTimer;
private bool myChanging = false;
#region public double DelayedValue
public double DelayedValue
{
get { return (double)GetValue(DelayedValueProperty); }
set { SetValue(DelayedValueProperty, value); }
}
public static readonly DependencyProperty DelayedValueProperty =
DependencyProperty.Register(
"DelayedValue",
typeof(double),
typeof(DelaySlider),
new PropertyMetadata(0.0, OnDelayedValuePropertyChanged));
private static void OnDelayedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DelaySlider source = d as DelaySlider;
if (source != null && !source.myChanging)
{
source.Value = (double)e.NewValue;
}
}
#endregion public double DelayedValue
#region public int Delay
public int Delay
{
get { return (int)GetValue(DelayProperty); }
set { SetValue(DelayProperty, value); }
}
public static readonly DependencyProperty DelayProperty =
DependencyProperty.Register(
"Delay",
typeof(int),
typeof(DelaySlider),
new PropertyMetadata(0, OnDelayPropertyChanged));
private static void OnDelayPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DelaySlider source = d as DelaySlider;
if (source != null)
{
source.OnDelayPropertyChanged((int)e.OldValue, (int)e.NewValue);
}
}
private void OnDelayPropertyChanged(int oldValue, int newValue)
{
if (myTimer != null)
{
myTimer.Stop();
myTimer = null;
}
if (newValue > 0)
{
myTimer = new DispatcherTimer();
myTimer.Tick += myTimer_Tick;
myTimer.Interval = TimeSpan.FromMilliseconds(newValue);
}
}
void myTimer_Tick(object sender, EventArgs e)
{
myTimer.Stop();
myChanging = true;
SetValue(DelayedValueProperty, Value);
myChanging = false;
}
#endregion public int Delay
protected override void OnValueChanged(double oldValue, double newValue)
{
base.OnValueChanged(oldValue, newValue);
if (myTimer != null)
{
myTimer.Start();
}
}
}
Now replace your Silder with DelaySlider and bind your View-Model property to the DelayedValue and specify your millisecond delay value in its Delay property.
You now have a useful re-usable control, you haven't messed about with nasty tricks in the View, you have no additional code in the code-behind of the view, the View-Model is unchanged and undisturbed and you haven't had to do include the Rx stuff at all.