I'm enhancing an open source control to add some functionality that I need, and I'm getting hopelessly tangled up in the following problem:
The control is a rich textbox that supports HTML but not via a property; you have to do something like this:
var sHtml = "..."
ctrl.LoadHtml(sHtml)
and
var sHtml = ctrl.SaveHtml()
So far so good. But I want to set the HTML via data binding, so I made a dependency property called Html:
public static readonly DependencyProperty HtmlProperty =
DependencyProperty.Register(
"Html",
typeof(string),
typeof(RichTextEditor),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(HtmlChangedCallback))
);
public string Html
{
get {return (string)GetValue(HtmlProperty);}
set {SetValue(HtmlProperty, value);}
}
private static void HtmlChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//get the control
var rte = (RichTextEditor)d;
//got here, so load the html
rte.TextBox.LoadHtml((string)e.NewValue);
}
This all works fine. The problem I'm having is that I can't figure out how to notify the property system when the contents of the control have changed. The control has a ContentChanged event, so I tried this:
private void rtb_ContentChanged(object sender, RichTextBoxEventArgs e)
{
//tell the html prop that it changed
SetValue(HtmlProperty, rtb.SaveHtml());
}
But this then triggers the HtmlChangedCallback and the re-entrance caused problems. So then I tried using a re-entrance flag, but that got messy because the sequence of events is more complex than I would have expected, and around this point I figured I must be missing something, so I'm asking here. Please help! Thanks in advance.
BTW, the control doesn't support INotifyPropertyChanged, and implementing it is out of scope, because the control is big and I don't want to do that much work.
This is a classic little problem that is usually solved with a simple boolean flag.
private bool suppressHtmlChanged = false;
private static void HtmlChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rte = (RichTextEditor)d;
if (!rte.suppressHtmlChanged)
rte.TextBox.LoadHtml((string)e.NewValue);
}
private void rtb_ContentChanged(object sender, RichTextBoxEventArgs e)
{
suppressHtmlChanged = true;
SetValue(HtmlProperty, rtb.SaveHtml());
suppressHtmlChanged = false;
}
These days such solutions seem so old fashioned don't they, often we can convince ourselves that such a "low-tech" solutions can't be right because they're not "elegant".
Why do you need to notify property system? Are you trying to update the source of the data binding? The control does not have to implement INotifyPropertyChanges; this interface has to be implemented on the class which provides the data for data binding.
What if you ignore Save/LoadHtml for now and just store HTML as a string? Does it work without problems?
Here is the example which works fine for me
class MyControl
{
public int Value
{
get
{
return (int)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
Count.Text = value.ToString();
}
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(int),
typeof(Vote),
new PropertyMetadata(0, OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl)d).Value = (int)e.NewValue;
}
Count is a text block control.
Related
I have a button on a WPF window that adds a tabitem to a tabcontrol on the window. A user control that fills that tabitem is also created in that event. I want to set the focus to a textbox on that user control. I've tried all kinds of txt.Focus code and so forth but the button always still have focus after the click event is executed.
I can't use this as the textbox is not part of the xaml on this window at design time.
MyAttachedProps:EventFocusAttachment.ElementToFocus="{Binding ElementName=NotAvailableAtDesignTime}"
The only thing I can think of is a timer to execute after the button click but there has to be a better way.
You could use a bool attached dependency property.
Bind this to a public bool property in your viewmodel.
Set that to true when you want to focus the control.
I have a bit of code. Can't recall if I wrote it or grabbed it off the web. And I can't recall actually using it either.
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static async void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
await Task.Delay(200);
uie.Focus();
Keyboard.Focus(uie);
}
}
}
This the await task delay introduces a 200ms wait so other stuff can finish whatever it's doing. You could instead defer the focussing using dispatcher.
Application.Current.Dispatcher.InvokeAsync(new Action(() =>
{
uie.Focus();
Keyboard.Focus(uie);
}), DispatcherPriority.ContextIdle);
Due to closures, that code will capture whatever uie is so long as it's in scope.
I have a custom class, MyPerson. All (relevant) properties implement INotifyPropertyChanged.
I created a UserControl to display it, and it all worked fine. Binding to properties like MyPerson.FirstName (a string) all work - they display and update (two way binding) as expected.
Now I want to do more complex stuff in the codebehind, so I wanted to create a DependencyProperty with a PropertyType of MyPerson, but I'm not sure how to construct the DependencyProperty, in particular the PropertyChangedCallback part.
Can this be done? How so?
Read on this article - Custom Dependency Properties
Something like -
public static readonly DependencyProperty MyPersonValueProperty =
DependencyProperty.Register( "MyPersonValue", typeof(MyPerson),
typeof(MyPersonControl), new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnPersonChanged) ) );
public MyPerson ThePerson
{
get { return (MyPerson)GetValue(MyPersonValueProperty); }
set { SetValue(MyPersonValueProperty, value); }
}
private static void OnPersonChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// Property change code here
}
I'm trying to create a custom markup extension using IMarkupExtension<T> that has some DependencyProperties for binding. However, I am struggling to resolve the problem of the markup extension being resolved at XAML parse time, and the bindings only later. I don't seem to ever get something through the bindings: they're always null and never call their change callback.
The docs mention something about returning the instance of the markup extension (under "Returning the Current Markup Extensions Instance"), but that seems to make stuff explode because it's the wrong type for the target. This SL5 MultiBinding seems to return a proxy binding to an internal source object, but I can't manage to get that working: my bindings still don't ever set.
I can't seem to find any solid information how how to actually implement markup extensions with DependencyProperties (even though it seemed like something a lot of people were excited about with SL5...). Can anyone offer any guidance or tutorials?
Specifically, what I'm trying to do is create a markup extension that can dynamically construct a path to do a binding to a list, like so:
{my:ListLookup ListPath='List' Index={Binding Index}}
I'm wanting it to basically output a Binding that would look like {Binding List[Index]}, where Index is dynamic. The purpose of doing this over, say, a MultiBinding on the list and index, is so that we are binding directly to the object and get change notifications. (If there's a better way of doing this...)
I've fiddled with this a lot more and I've found the solution. It's based on the implementation of the SL5 MultiBinding that I linked to in the question.
The trick is that a Binding on a MarkupExtension will never be evaluated because it doesn't have a DataContext or something, but if you take the BindingExpression from it and throw it into a proxy Attached Property (attached to the target object) then you can get the Binding to resolve.
Below is a simple MarkupExtension that demonstrates this. All it's doing is taking a single Binding and outputting its value (obeying changes appropriately), but it shows how it holds together. This can be extended to solve the dictionary issue I was talking about, along with this problem in general.
public class SimpleBindingMarkupExtension : DependencyObject, IMarkupExtension<object>, INotifyPropertyChanged
{
public object Binding
{
get { return (object)GetValue(BindingProperty); }
set { SetValue(BindingProperty, value); }
}
public static readonly DependencyProperty BindingProperty =
DependencyProperty.Register(
"Binding",
typeof(object),
typeof(SimpleBindingMarkupExtension),
new PropertyMetadata(null));
public static readonly DependencyProperty ProxyAttachedBindingProperty =
DependencyProperty.RegisterAttached(
"ProxyAttachedBinding",
typeof(object),
typeof(SimpleBindingMarkupExtension),
new PropertyMetadata(null, OnProxyAttachedBindingChanged));
public static readonly DependencyProperty AttachedMarkupExtensionProperty =
DependencyProperty.RegisterAttached(
"AttachedMarkupExtension",
typeof(SimpleBindingMarkupExtension),
typeof(SimpleBindingMarkupExtension),
new PropertyMetadata(null));
private object _bindingSource;
public object BindingSource
{
get { return _bindingSource; }
set
{
_bindingSource = value;
OnPropertyChanged("BindingSource");
}
}
private static void OnProxyAttachedBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Pull the MarkupExtension from the attached property
var markupExtension = (SimpleBindingMarkupExtension) d.GetValue(AttachedMarkupExtensionProperty);
markupExtension.ProxyAttachedBindingChanged(e.NewValue);
}
private void ProxyAttachedBindingChanged(object value)
{
BindingSource = value;
}
public object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = (IProvideValueTarget) serviceProvider.GetService(typeof (IProvideValueTarget));
DependencyObject targetObject = target.TargetObject as DependencyObject;
if (targetObject == null)
return null;
// Attach this MarkupExtension to the object so we can find it again from attached property change callbacks
targetObject.SetValue(AttachedMarkupExtensionProperty, this);
// Put binding onto proxy attached property, so it actually evaluates
var localValue = ReadLocalValue(BindingProperty);
var bindingExpression = localValue as BindingExpression;
if (bindingExpression == null)
{
return localValue;
}
Binding originalBinding = bindingExpression.ParentBinding;
BindingOperations.SetBinding(targetObject, ProxyAttachedBindingProperty, originalBinding);
// Give the target a proxy Binding that binds to a property on the MarkupExtension
Binding binding = new Binding
{
Path = new PropertyPath("BindingSource"),
Source = this
};
return binding.ProvideValue(serviceProvider);
}
#region INotifyPropertyChanged
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Usage:
<TextBlock Text="{local:SimpleBindingMarkupExtension Binding={Binding Text}}"/>
As mentioned, this example will produce the same result as just saying Text="{Binding Text}", but shows the solution.
We use the MVVM pattern. In the View, I have binding the save command to a button:
In the view model, I would like to find out the save command binding target, does it possible?
private Button GetBindingControl(ICommand command)
{
// What should I do here:
return button;
}
It's not possible, and it defeats the purpose of MVVM (having the UI logic in the VM regardless of the controls used)
Maybe you could ask instead what problem you are trying to solve.
As #Diego said, this defats the purpose of MVVM because we must try hard not to include visuals or controls in the view models in MVVM...
Having said that there are two options...
Using RoutedCommands
Using Attached Behaviors.
RoutedCommands are not readily allowed in MVVM as they need to be closely command bound to the UI element i.e. in our case the Button. Hence they too defeat the purpose of MVVM.
But MVVM happily co-exists with the Attached Behaviors.
Many developers shy away from this immensely powerful feature. And we can use it along with RoutedCommands.
In your case
Attach to the Button, with a Action delegate.
Attach the string object as command parameter.
Inside the behavior, set the Button.Command with some Routed command.
In the executed event handler, get the button action delegate from the sender / originalsource / source as the button and then call your Action<> accordingly by using e.Parameter string value.
Sample code below...
Assume you have common button utilities of signature Action<Button, string>
public static class ButtonActionUtilities
{
public static Action<Button, string> ButtonActionDelegate
{
get
{
return ExecuteButtonClick;
}
}
public static void ExecuteButtonClick(Button btn, string param)
{
MessageBox.Show(
"You clicked button " + btn.Content + " with parameter " + param);
}
}
Then the attched behavior is as below...
public static class ButtonAttachedBehavior
{
public static readonly DependencyProperty ActionDelegateProperty
= DependencyProperty.RegisterAttached(
"ActionDelegate",
typeof(Action<Button, string>),
typeof(ButtonAttachedBehavior),
new PropertyMetadata(null, OnActionDelegatePropertyChanged));
public static Action<Button, string> GetActionDelegate(
DependencyObject depObj)
{
return (Action<Button, string>)depObj.GetValue(
ActionDelegateProperty);
}
public static void SetActionDelegate(
DependencyObject depObj, Action<Button, string> value)
{
depObj.SetValue(ActionDelegateProperty, value);
}
private static void OnActionDelegatePropertyChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
if (depObj is Button
&& e.NewValue is Action<Button, string>)
{
((Button)depObj).Command
= new RoutedCommand(
"ActionRoutedCommand",
typeof(ButtonAttachedBehavior));
((Button) depObj).CommandBindings.Add(
new CommandBinding(
((Button) depObj).Command,
OnActionRoutedCommandExecuted));
}
}
private static void OnActionRoutedCommandExecuted(
object sender, ExecutedRoutedEventArgs e)
{
var actionDelegate = GetActionDelegate((Button)e.Source);
actionDelegate((Button) e.Source, (string)e.Parameter);
}
}
And on XAML it will look like this....
<StackPanel>
<Button x:Name="TestButton" Content="Test Me"
local:ButtonAttachedBehavior.ActionDelegate
="{x:Static local:ButtonActionUtilities.ButtonActionDelegate}"
CommandParameter
="{Binding Text, ElementName=ParameterTextBox}"/>
<TextBox x:Name="ParameterTextBox"/>
</StackPanel>
So with the code above you will need to just set the ActionDelegate attached property to approapriate delegate and it will execute that.
I would still suggest you to revamp your existing code setup to separate button specific behaviors to make it more MVVM friendly.
I am trying to add my own ItemsSource to provide a list of GraphViewModels to a chart. I dont think I have it quite right though, as when I create my first GraphViewModel and add it to the Graphs, my DP is updated, but OnGraphsCollectionChanged is not called.
How is this supposed to work? If I add graphs to my VM property via a button tied to a command then all is good.
Here is the DP code, can anyone explain how this is supposed to work or what Im doing wrong to display my data during initialization?
public static readonly DependencyProperty GraphsProperty =
DependencyProperty.Register("ItemsSource",
typeof(ObservableCollection<GraphViewModel>),
typeof(DynamicPlotter),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeGraphs)));
public ObservableCollection<GraphViewModel> ItemsSource
{
get { return (ObservableCollection<GraphViewModel>)GetValue(GraphsProperty); }
set
{
SetValue(GraphsProperty, value);
ItemsSource.CollectionChanged += new NotifyCollectionChangedEventHandler(OnGraphsCollectionChanged);
}
}
public static void ChangeGraphs(DependencyObject source, DependencyPropertyChangedEventArgs eventArgs)
{
(source as DynamicPlotter).UpdateGraphs((ObservableCollection<GraphViewModel>)eventArgs.NewValue);
}
private void UpdateLineGraphs(ObservableCollection<GraphViewModel> grphs)
{
this.ItemsSource = grphs;
}
private void OnGraphsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// This never gets called when you set the ItemsSource, but is where all the work is done
}
CollectionChanged only gets called when the collection changes, if the collection is already populated prior to being set you will never get your notification, until something gets added/removed.
Secondly, if you are setting the dependency property from xaml the getter/setter is not used, the dependency mechanism uses its own internal setter routines. You should attach your collectionChanged event in your ChangeGraphs property callback function as this is called whenever the property is set/changed. You can use this to unhook the old collectionChanged event as well, the event arguments will give you an old and new value.
But really, it is an observable collection you should not have to know when the collection changes as you should be binding to the collection and when it changes the binding mechanism will update your ui.
I would change my code to look like this
public ObservableCollection<GraphViewModel> ItemsSource {
get { return (ObservableCollection<GraphViewModel>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableCollection<GraphViewModel>), typeof(DynamicPlotter), new UIPropertyMetadata(null, (o, e) => { ((DynamicPlotter)o).ItemsSourceChanged(); }));
private void ItemsSourceChanged() {
if (this.ItemsSource != null){
//attach the collection changed listener, this will listen to all FUTURE collection changes, items that are added and removed
this.ItemsSource.CollectionChanged +=new NotifyCollectionChangedEventHandler(ItemsSource_CollectionChanged);
//do some inital processing with the items that are in the collection already when it is set
this.UpdateGraphs(this.ItemsSource);
}
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e){
//this will get called if an item gets added or removed from the collection
}