Simple question. Having:
<ScrollBar ... />
How can I detect when Maximum is changed? E.g. for Value there is an event.
Typically there would be a binding of some kind. I was thinking maybe it is possible to get this binding, create dependency property and bind to it instead, then I can register a callback when this new dependency property is changed... but that sounds complicated nor I am sure it is acceptable solution to all cases (e.g. what if another binding is set, how can I detect this kind of change). Polling?
You can create a custom class such as:
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
{
// do stuff
base.OnMaximumChanged(oldMaximum, newMaximum);
}
}
Or
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == System.Windows.Controls.Primitives.ScrollBar.MaximumProperty)
{
// do stuff
}
base.OnPropertyChanged(e);
}
}
It is important to understand what any property can be a source for multiple bindings. We can create a new target (new dependency property) which is then perfectly able to report about any change done to a property:
Create a new dependency property with callback.
Bind it to any other property to monitor for changes.
public partial class MainWindow : Window
{
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(MainWindow), new PropertyMetadata(0, (d, e) =>
{
// value has changed
}));
public MainWindow()
{
InitializeComponent();
var scrollBar = ... // instance of scrollbar
BindingOperations.SetBinding(this, MaximumProperty,
new Binding(nameof(RangeBase.Maximum)) { Source = scrollBar });
}
}
Related
I'm trying to make a composite behavior which is composed of arbitrary simple behaviors. I found behaviors very flexible way of making custom controls.
Currently I have 5 behaviors implemented for slider. but they can conflict with each other.
These behaviors are designed for one control. I could design each of them to work independently without conflicting with each other (its worth mentioning that I did this and it worked successfully. but I removed all of it because it was just ugly.)
There are a lot of share points, I don't want to rewrite same code for every behavior.
So I'm trying to make a composite behavior for one control. this behavior has some attached properties which is shared for all of its containing behaviors. therefor these behaviors don't conflict with each other. And also a lot of code redundancy is gone. now containing behaviors becomes a lot simpler.
Here is the XAML sample for you to better get the idea.
<i:Interaction.Behaviors>
<b:SliderCompositeBehavior SourceValue="{Binding SharedValue}">
<sb:FreeSlideBehavior/>
<sb:LockOnDragBehavior/>
<sb:CancellableDragBehavior/>
<sb:KeepRatioBehavior/>
<sb:DragCompletedCommandBehavior Command="{Binding SeekTo}"/>
</b:SliderCompositeBehavior>
</i:Interaction.Behaviors>
Also all of these behaviors are designed to work stand alone. i.e putting it like this works just fine.
<i:Interaction.Behaviors>
<sb:FreeSlideBehavior/>
</i:Interaction.Behaviors>
Here is CompositeBehavior<T> : Behavior<T> :
[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
where T : DependencyObject
{
public static readonly DependencyProperty BehaviorCollectionProperty =
DependencyProperty.Register(
$"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
typeof(ObservableCollection<Behavior<T>>),
typeof(CompositeBehavior<T>),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.NotDataBindable));
public ObservableCollection<Behavior<T>> BehaviorCollection
{
get
{
var collection = GetValue(BehaviorCollectionProperty) as ObservableCollection<Behavior<T>>;
if (collection == null)
{
collection = new ObservableCollection<Behavior<T>>();
collection.CollectionChanged += OnCollectionChanged;
SetValue(BehaviorCollectionProperty, collection);
}
return collection;
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// some code to throw exception when same behavior is set more than once.
}
protected override void OnAttached()
{
foreach (var behavior in BehaviorCollection)
{
behavior.Attach(AssociatedObject);
}
}
protected override void OnDetaching()
{
foreach (var behavior in BehaviorCollection)
{
behavior.Detach();
}
}
}
Here is the SliderCompositeBehavior : CompositeBehavior<Slider> (only one dependency is shown for sake of simplicity)
public sealed class SliderCompositeBehavior : CompositeBehavior<Slider>
{
private Slider Host => AssociatedObject;
public static readonly DependencyProperty SourceValueProperty =
DependencyProperty.Register(
nameof(SourceValue),
typeof(double),
typeof(SliderCompositeBehavior),
new FrameworkPropertyMetadata(
0d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSourceValueChanged));
// does the binding
public double SourceValue
{
get { return (double)Host.GetValue(SourceValueProperty); }
set { Host.SetValue(SourceValueProperty, value); }
}
// attached property for containing behaviors.
public static void SetSourceValue(Slider host, double value)
{
host.SetValue(SourceValueProperty, value);
}
public static double GetSourceValue(Slider host)
{
return (double)host.GetValue(SourceValueProperty);
}
private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var soruce = (SliderCompositeBehavior)dpo;
soruce.Host.Value = (double)args.NewValue;
}
}
Now there are two problems I can see.
Dependency property definitions inside containing behaviors do not function at all.
Overrinding metadata of dependency property does not work for containing properties.
Inside DragCompletedCommandBehavior : Behavior<Slider> I have
public sealed class DragCompletedCommandBehavior : Behavior<Slider>
{
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
nameof(Command),
typeof(ICommand),
typeof(DragCompletedCommandBehavior));
}
I get this error on output. (this does not throw exception. it was hidden somewhere in output display after program started.)
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=SeekTo; DataItem=null; target element is 'DragCompletedCommandBehavior' (HashCode=52056421); target property is 'Command' (type 'ICommand')
In another behavior I have this.
public sealed class LockOnDragBehavior : Behavior<Slider>
{
static LockOnDragBehavior()
{
SliderCompositeBehavior.SourceValueProperty.OverrideMetadata(
typeof(LockOnDragBehavior),
new FrameworkPropertyMetadata(
0d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSourceValueChanged));
}
private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
// do something
}
}
But OnSourceValueChanged never fires. the main OnSourceValueChanged inside SliderCompositeBehavior still fires though. but new meta data is just doing nothing.
How can I fix these problems? I don't understand why Dependency properties inside containing behaviors does not work. can some one please explain why? Thank you so much.
I found it out. after reading this post I understood that elements (in my case nested behaviors) were not part of the visual or logical tree. So data context was not accessible. and therefor binding did not work.
But instead of using ProxyBinding which was used here I came up with better solution.
The special collection BehaviorCollection does some magic when attaching behaviors. but I was using ObservableCollection therfor behaviors did not attached correctly.
Unfortunately the constructor of BehaviorCollection is internal. But who cares when you have power of reflection? ;)
Using BehaviorCollection instead essentially fixed the binding problem.
how ever overriding metadata problem is still not fixed. but I guess I will try other approaches (like using another dependency property) rather than overriding metadata of dependency property.
Here is the correction to CompositeBehavior<T> class.
[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
where T : DependencyObject
{
#region Behavior Collection
public static readonly DependencyProperty BehaviorCollectionProperty =
DependencyProperty.Register(
$"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
typeof(BehaviorCollection),
typeof(CompositeBehavior<T>),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.NotDataBindable));
public BehaviorCollection BehaviorCollection
{
get
{
var collection = GetValue(BehaviorCollectionProperty) as BehaviorCollection;
if (collection == null)
{
var constructor = typeof(BehaviorCollection)
.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, Type.EmptyTypes, null);
collection = (BehaviorCollection) constructor.Invoke(null);
collection.Changed += OnCollectionChanged;
SetValue(BehaviorCollectionProperty, collection);
}
return collection;
}
}
private void OnCollectionChanged(object sender, EventArgs eventArgs)
{
var hashset = new HashSet<Type>();
foreach (var behavior in BehaviorCollection)
{
if (behavior is Behavior<T> == false)
{
throw new InvalidOperationException($"{behavior.GetType()} does not inherit from {typeof(Behavior<T>)}.");
}
if (hashset.Add(behavior.GetType()) == false)
{
throw new InvalidOperationException($"{behavior.GetType()} is set more than once.");
}
}
}
#endregion
protected sealed override void OnAttached()
{
OnSelfAttached();
foreach (var behavior in BehaviorCollection)
{
behavior.Attach(AssociatedObject);
}
}
protected sealed override void OnDetaching()
{
OnSelfDetaching();
foreach (var behavior in BehaviorCollection)
{
behavior.Detach();
}
}
protected virtual void OnSelfAttached()
{
}
protected virtual void OnSelfDetaching()
{
}
}
In developing some UserControls for internal use I followed this exmaple from MSDN http://msdn.microsoft.com/en-us/library/vstudio/ee712573(v=vs.100).aspx
The public value of one control is used by another control. The way I have this working currently is hooking into an event that is fired in the first control through code-behind. I am thinking that making one or both of the properties DependencyProperties which would eliminate the need for the code-behind.
public partial class UserControl1 : UserControl
{
private DataModel1 dm;
public UserControl1()
{
this.DataContext = new DataModel1();
dm = (DataModel1)DataContext;
InitializeComponent();
}
public DataValue CurrentValue
{
get { return dm.CurrentValue; }
set { dm.CurrentValue = value; }
}
}
public class DataModel1 : INotifyPropertyChanged
{
private DataValue _myData = new DataValue();
public DataValue CurrentValue
{
get { return _myData; }
set { if (_myData != value) {_myData = value OnPropertyChanged("CurrentValue"); }
}
// INotifyPropertyChanged Section....
}
The property is just a pass through from the DataModel1 class.
Both UserControls are very similar in their structure and have the same public properties. I would like to replace the code behind eventhandler with a Binding similar, I think to:
<my:UserControl1 Name="UserControl1" />
<my:UserControl2 CurrentValue={Binding ElementName="UserControl1", Path="CurrentValue"} />
but the standard examples of DependencyProperties have getters and setter that use the GetValue and SetValue functions which use a generated backing object instead of allowing a pass through.
public DataValue CurrentValue
{
get { return (DataValue)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
I think the DP should look like:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1));
How can I change the definition of the public backing property to support the databinding pass through?
I found that jumping into the OnPropertyChanged event allowed me to pass the data through to the DataModel1. I am not 100% sure that this is the correct answer but it gets the job done.
Here is the corrected code:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1),
new PropertyMetadata(new PropertyChangedCallback(OnCurrenValueChanged)));
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
if (e.NewValue != null)
{
uc.dm.CurrentValue = e.NewValue as DataValue;
}
}
public DataValue CurrentValue
{
get { return GetValue(CurrentValueProperty) as DataValue; }
set { SetValue(CurrentValueProperty, value); }
}
I'm having some problems with the usage of an Dependency Property. I would like to use the value of the DP to initialize an object in the constructor.
The problem is that Month is always 0 (during construction time) which causes the wrong initialization of the ExpenseDetailPageDataModel. Right after the constructor finished his work the value of variable Month changes to correct value (in this case 11).
FinanceItemViewControl is a custom user control.
<common:FinanceItemViewControl Grid.Column="2" Month="11"/>
Month is a Dependency Property as shown in the code below:
public sealed partial class FinanceItemViewControl : UserControl
{
...
public static readonly DependencyProperty MonthProperty = DependencyProperty.Register
(
"Month",
typeof(int),
typeof(FinanceItemViewControl),
new PropertyMetadata(
0, new PropertyChangedCallback(MonthProperty_Changed))
);
public int Month
{
get { return (int)GetValue(MonthProperty); }
set { SetValue(MonthProperty, value); }
}
#endregion
private static void MonthProperty_Changed(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
//TODO: trigger data reload
}
public FinanceItemViewControl()
{
this.InitializeComponent();
...
Debug.WriteLine("Constructor: " + Month);
detailPageDataModel = new ExpenseDetailPageDataModel(Month);
...
}
You can't put that logic in the constructor because, as you noticed, the Data Context hasn't loaded yet. You could do one of two things:
Put the logic inside the MonthProperty_Changed event.
Use the control's Loaded event:
public FinanceItemViewControl()
{
this.InitializeComponent();
detailPageDataModel = new ExpenseDetailPageDataModel(Month);
this.Loaded += UserControl_Loaded;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Constructor: " + Month);
}
How do I set default values for my custom wpf components?
I have a Textfield with the property "public Protection Protection{set;get;}". Protection is an enum:
public class Field3270Attributes{
public enum Protection
{
PROTECTED,
UNPROTECTED,
AUTOSKIP
}
}
The default value should be autoskip but protected is listed as the default value in the wpf designer since its the first element in the enum.
Setting the protection in the Textfield constructor did not help.
I've tried DependencyProperty which does work but I have to specify a callback(setProtection) if I want any values except the default value to work.
If I dont specify a callback, changing values inside the wpf designer has no effect.
Is there a way to get the same behavior without having to specify callback methods for every property?
public class Textfield{
public static readonly DependencyProperty ProtectionProperty =
DependencyProperty.Register("Protection",
typeof(Field3270Attributes.Protection),
typeof(Textfield3270),
new FrameworkPropertyMetadata(Field3270Attributes.Protection.PROTECTED, setProtection));
private static void setProtection(object sender, DependencyPropertyChangedEventArgs e)
{
Textfield field = (Textfield)sender;
field.Protection = (Field3270Attributes.Protection)e.NewValue;
}
private Field3270Attributes.Protection protection;
public Field3270Attributes.Protection Protection
{
get
{
return protection;
}
set
{
this.protection = value;
if (value == Field3270Attributes.Protection.UNPROTECTED)
{
this.IsReadOnly = false;
Background = Brushes.White;
}
else
{
this.IsReadOnly = true;
Background = Brushes.LightSteelBlue;
}
}
}
public Textfield3270()
{
this.Protection = Field3270Attributes.Protection.PROTECTED;
}
}
Your DependencyProperty definition defines the default value. Change the first parameter in the FrameworkPropertyMetadata from PROTECTED to AUTOSKIP
public static readonly DependencyProperty ProtectionProperty =
DependencyProperty.Register("Protection",
typeof(Field3270Attributes.Protection),
typeof(Textfield3270),
new FrameworkPropertyMetadata(Field3270Attributes.Protection.AUTOSKIP, setProtection));
EDIT
You are overwriting your Protection DependencyProperty by implementing your own version with the same name. Remove your definition of the Protection {get; set;} completely.
If you want them to show up in the XAML designer, define the Get/Set as static methods like so:
public static Field3270Attributes.Protection GetProtection(DependencyObject obj)
{
return (Field3270Attributes.Protection)obj.GetValue(ProtectionProperty);
}
public static void SetProtection(DependencyObject obj, Field3270Attributes.Protection value)
{
obj.SetValue(ProtectionProperty, value);
}
If you want to attach some logic on PropertyChanged, you can use this code in your class constructor:
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(Textfield.ProtectionProperty, typeof(Textfield));
if (dpd != null) dpd.AddValueChanged(this, delegate { Protection_Changed(); });
And your changed method would look like this:
private void Protection_Changed()
{
Field3270Attributes.Protection protection = GetProtection(this);
// Do something with value
}
I am using the Model-View-ViewModel architecture in a WPF application I am building, and I would like a specific ViewModel to actually be reactive to the size of the view (not a normal use-case of the MVVM approach, I know).
Essentially, I have a ScrollViewer object and I want the viewmodel to observe the width and height of the scrollviewer and then be able to do things accordingly depending on what that width and height are.
I'd like to do something like this:
<ScrollViewer ViewportWidth="{Binding Path=MyViewportWidth, Mode=OneWayToSource}" ViewportHeight="{Binding Path=MyViewportHeight, Mode=OneWayToSource}" />
But of course this is impossible to do because "ViewportWidth" and "ViewportHeight" cannot be "bound to" (a.k.a. act as binding targets) because they are read-only dependency properties (even though I am not writing to them at all in this binding since it is OneWayToSource).
Anyone know of a good method to be able to do something like this?
You could try running something OnLoaded or OnResizeChanged that updates the viewmodel
private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
ViewModel vm = sv.DataContext as ViewModel;
vm.ScrollViewerHeight = sv.ViewportHeight;
vm.ScrollViewerWidth = sv.ViewportWidth;
}
Ok, this is a really old question, but I thought I'd share for posterity, since I've solved this one myself. The best solution I've found is to create a user control that derives from the ScrollView class and implements the properties you want - which are of course linked to the non-bindable properties of the base class.
You can use the OnPropertyChanged function to monitor those properties and keep the values in sync.
Here's the full code-behind of my custom usercontrol called DynamicScrollViewer. Notice that I have four bindable dependency properties called DynamicHorizontalOffset, DynamicVerticalOffset, DynamicViewportWidth, and DynamicViewportHeight.
The two offset properties allow both read and write control of the offset, while the viewport properties are essentially read-only.
I had to use this class when creating a complex animation editor control in which various components (labels at the left, nodes in the middle, timeline at top) needed to scroll synchronously, but only in limited aspects, and were all bound to common external scrollbars. Think of locking a section of rows in spreadsheet, and you get the idea.
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public partial class DynamicScrollViewer : ScrollViewer
{
public DynamicScrollViewer()
{
InitializeComponent();
}
public double DynamicHorizontalOffset
{
get { return (double)GetValue(DynamicHorizontalOffsetProperty); }
set { SetValue(DynamicHorizontalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicHorizontalOffsetProperty =
DependencyProperty.Register("DynamicHorizontalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicVerticalOffset
{
get { return (double)GetValue(DynamicVerticalOffsetProperty); }
set { SetValue(DynamicVerticalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicVerticalOffsetProperty =
DependencyProperty.Register("DynamicVerticalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportWidth
{
get { return (double)GetValue(DynamicViewportWidthProperty); }
set { SetValue(DynamicViewportWidthProperty, value); }
}
public static readonly DependencyProperty DynamicViewportWidthProperty =
DependencyProperty.Register("DynamicViewportWidth", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportHeight
{
get { return (double)GetValue(DynamicViewportHeightProperty); }
set { SetValue(DynamicViewportHeightProperty, value); }
}
public static readonly DependencyProperty DynamicViewportHeightProperty =
DependencyProperty.Register("DynamicViewportHeight", typeof(double), typeof(DynamicScrollViewer));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DynamicVerticalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetVerticalOffset(DynamicVerticalOffset);
}
else if (e.Property == DynamicHorizontalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetHorizontalOffset(DynamicHorizontalOffset);
}
else if (e.Property == HorizontalOffsetProperty)
{
DynamicHorizontalOffset = (double)e.NewValue;
}
else if (e.Property == VerticalOffsetProperty)
{
DynamicVerticalOffset = (double)e.NewValue;
}
else if (e.Property == ViewportWidthProperty)
{
DynamicViewportWidth = (double)e.NewValue;
}
else if (e.Property == ViewportHeightProperty)
{
DynamicViewportHeight = (double)e.NewValue;
}
}
}
}