I am facing an issue with my code below where the call back is getting called only the first time DocText is set. Subsequently setting the DocText is not calling my callback. I am trying to reset the html of the browser by setting the same string to the DocText property.
WebBrowser class
public static readonly DependencyProperty DocumentTextProperty =
DependencyProperty.Register("DocumentText", typeof(string),
typeof(WebBrowser), new
FrameworkPropertyMetadata(string.Empty,TextChangedCallback));
private static void TextChangedCallback(DependencyObject
dependencyObject, DependencyPropertyChangedEventArgs
dependencyPropertyChangedEventArgs)
{
var control = (WebBrowser)dependencyObject;
control._browser.DocumentText = dependencyPropertyChangedEventArgs.NewValue.ToString();
}
public string DocumentText
{
get { return (string)GetValue(DocumentTextProperty); }
set { SetValue(DocumentTextProperty, value); }
}
Factory class that creates the instance of the web browser is using the browsers set binding to bind the dependency property to "DocText"
if (!String.IsNullOrEmpty(documentTextProperty))
{
browser.SetBinding(WebBrowser.DocumentTextProperty, documentTextProperty);
}
And the documentTextProperyt is being set like this from the ViewModel class
DocText = "some html string";
public string DocText
{
get
{
_docText = if(html != null? html: string.empty);
return _docText;
}
set
{
docText = value;
OnPropertyChanged(() = DocText);
}
}
Looks like the Notification is not going back to control(WebBrowser). Please check whether INotifyPropertyChanged is properly implemented. I checked equivalent code in my system and it is properly working.
public class MaskedTextBox : TextBox
{
public static readonly DependencyProperty DocumentTextProperty =
DependencyProperty.Register("DocumentText", typeof(string),
typeof(MaskedTextBox), new PropertyMetadata(default(string), TextChangedCallback));
private static void TextChangedCallback(DependencyObject
dependencyObject, DependencyPropertyChangedEventArgs
dependencyPropertyChangedEventArgs)
{
var control = (MaskedTextBox)dependencyObject;
control.Text= dependencyPropertyChangedEventArgs.NewValue.ToString();
}
public string DocumentText
{
get { return (string)GetValue(DocumentTextProperty); }
set { SetValue(DocumentTextProperty, value); }
}
}
<StackPanel Margin="50,5,5,50">
<local:MaskedTextBox x:Name="text1" DocumentText="{Binding ElementName=text2, Path=Text, Mode=OneWay}"/>
<TextBox x:Name="text2"/>
</StackPanel>
Here if you edit second text box it is updating first one by calling the callback method properly!
Related
I have a custom control with a label on it. This control has a property Label like this:
public string Label
{
get { return (string)GetValue(LabelProperty); }
set
{
label.Text = value;
SetValue(LabelProperty, value);
}
}
public static DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(SuperButton), new PropertyMetadata(null));
Note, label with small l is an internal textblock. SuperButton is the name of the control.
Then I have this simple object:
class Student : INotifyPropertyChanged
{
public string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged( new PropertyChangedEventArgs("Name")); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
So then I bind with this XAML:
<UIFragments:SuperButton Margin="531,354,555,367" Label="{Binding Name}"></UIFragments:SuperButton>
And then I have this in the same page as the button instance
Student s = new Student { Name = "John Smith" };
DataContext = s;
I have tried setting the control's datacontext to itself but nothing is working. Setting the Label to a string works.
If I use the data binding the set{} block is never fired...
XAML doesn't call your Setter method, as is pointed out at MSDN:
The WPF XAML processor uses property system methods for dependency
properties when loading binary XAML and processing attributes that are
dependency properties. This effectively bypasses the property
wrappers. When you implement custom dependency properties, you must
account for this behavior and should avoid placing any other code in
your property wrapper other than the property system methods GetValue
and SetValue.
What you need to do is register a callback method that fires whenever the dependency property changes:
public static DependencyProperty LabelProperty = DependencyProperty.Register(
"Label",
typeof(string),
typeof(SuperButton),
new PropertyMetadata(null, PropertyChangedCallback)
);
Note the PropertyChangedCallback in the last line! This method is implemented as follows:
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
SuperButton userControl = ((SuperButton)dependencyObject);
userControl.label.Text = (string) args.NewValue;
}
The dependency property's getter and setter now can be reduced to:
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
Now whenever the Label property is changed, e.g. through binding it in your page, PropertyChangedCallback is called and that passes the text to your actual label!
I have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public MvvmTextEditor()
{
TextArea.SelectionChanged += TextArea_SelectionChanged;
}
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.SelectionLength = (int)args.NewValue;
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Now, in the view that holds this control, I have the following XAML:
<Controls:MvvmTextEditor
Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
TextLocation="{Binding TextLocation, Mode=TwoWay}"
SyntaxHighlighting="{Binding HighlightingDefinition}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
Document="{Binding Document, Mode=TwoWay}"/>
My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like
private int selectionLength;
public int SelectionLength
{
get { return selectionLength; }
set
{
if (selectionLength == value)
return;
selectionLength = value;
Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
NotifyOfPropertyChange(() => SelectionLength);
}
}
when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.
Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?
Thanks for your time.
This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.
What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.
public int SelectionLength
{
get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectionLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));
private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textEditor = obj as MvvmTextEditor;
textEditor.SelectionLength = e.NewValue;
}
Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.
The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.
Sample code below:
public class SomeBehavior
{
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject dpo, bool value)
{
dpo.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var editor = dpo as TextEditor;
if (editor == null)
return;
var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
}
private static void OnSelectionLengthChanged(object sender, EventArgs e)
{
var editor = (TextEditor)sender;
editor.Select(editor.SelectionStart, editor.SelectionLength);
}
}
Xaml below:
<Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
</Controls:TextEditor>
This is how I did this...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
//get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
Sorry for any time wasted. I hope this helps someone else...
I was creating a control for my WP8 app. I am trying to set value for dependency property irrespective of its default value. Below is my code
public BitmapImage EnabledImage { get; set; }
public BitmapImage DisabledImage { get; set; }
public bool ControlEnabled
{
get { return (bool)GetValue(ControlEnabledProperty); }
set { SetValue(ControlEnabledProperty, value); }
}
public static readonly DependencyProperty ControlEnabledProperty =
DependencyProperty.Register("ControlEnabled", typeof(bool), typeof(ucControl), new PropertyMetadata(OnImageStatePropertyChanged));
private static void OnImageStatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (d as ucControl);
control.ControlEnabled = Convert.ToBoolean(e.NewValue);
control.OnImageStateChanged(e.NewValue);
}
private void OnImageStateChanged(object newValue)
{
if (Convert.ToBoolean(newValue) == true)
imgControl.Source = EnabledImage;
else
imgControl.Source = DisabledImage;
}
this how i am calling it in xaml
<WP8:ucControl Grid.Row="0" Grid.Column="0" Height="92" Width="92" EnabledImage="/Images/img_on.png" DisabledImage="/Images/img_off.png" ControlEnabled="True"/>
it does not set the value when I set ControlEnabled = "False". Means disabled image is not set on image control.
I want this control to set property irrespective of it default value.
I refer this post as well but solution not working : Windows Phone 8, use DependencyProperty for a usercontrol, PropertyChangedCallback and CoerceValueCallback issue
Any Ideas what wrong here.
you can modify your code to this, and try it:
public static readonly DependencyProperty ControlEnabledProperty =
DependencyProperty.Register("ControlEnabled", typeof(bool?), typeof(ucControl), new PropertyMetadata(null, OnImageStatePropertyChanged));
Sorry to be cliche... but I'm pretty new to WPF and MVVM so I'm not sure how to handle this properly. I have a WinForms control within one of my views that I need to modify in it's code behind when an event is raised in the ViewModel. My view's datacontext is inherited so the viewmodel is not defined in the views constructor. How would I go about properly handling this? I am not using any frameworks with built in messengers or aggregators. My relevant code is below. I need to fire the ChangeUrl method from my ViewModel.
EDIT: Based on the suggestion from HighCore, I have updated my code. I am still not able to execute the ChangeUrl method however, the event is being raised in my ViewModel. What modifications need to be made??
UserControl.xaml
<UserControl ...>
<WindowsFormsHost>
<vlc:AxVLCPlugin2 x:Name="VlcPlayerObject" />
</WindowsFormsHost>
</UserControl>
UserControl.cs
public partial class VlcPlayer : UserControl
{
public VlcPlayer()
{
InitializeComponent();
}
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set
{
ChangeVlcUrl(value);
SetValue(VlcUrlProperty, value);
}
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null));
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
}
view.xaml
<wuc:VlcPlayer VlcUrl="{Binding Path=ScreenVlcUrl}" />
ViewModel
private string screenVlcUrl;
public string ScreenVlcUrl
{
get { return screenVlcUrl; }
set
{
screenVlcUrl = value;
RaisePropertyChangedEvent("ScreenVlcUrl");
}
}
WPF does not execute your property setter when you Bind the property, instead you must define a Callback method in the DependencyProperty declaration:
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set { SetValue(VlcUrlProperty, value); }
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null, OnVlcUrlChanged));
private static void OnVlcUrlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var player = obj as VlcPlayer;
if (obj == null)
return;
obj.ChangeVlcUrl(e.NewValue);
}
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
Is it possible to store a reference to a Page in a Custom Control so that when the control is clicked that page loads (like a custom menu item)?
My code so far:
public class ccMenuItem : Button
{
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(Control));
public static readonly DependencyProperty BackColorProperty = DependencyProperty.Register("BackColor", typeof(SolidColorBrush), typeof(Control), new UIPropertyMetadata(Brushes.White));
public static readonly DependencyProperty PageProperty = DependencyProperty.Register("Page", typeof(Page), typeof(Control));
public Page Page
{
get { return (Page)GetValue(PageProperty); }
set { SetValue(PageProperty, value); }
}
public string Title
{
get { return GetValue(TitleProperty).ToString(); }
set { SetValue(TitleProperty, value); }
}
static ccMenuItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ccMenuItem), new FrameworkPropertyMetadata(typeof(ccMenuItem)));
}
}
The code compiles OK, however how do I assing a page (say the class is called vpn) to the Page property in XAML?
If your "application" is using a NavigationWindow instead of Window, then you can get to the NavigationService and tell it to change the page.
protected override void OnClick()
{
NavigationService ns = NavigationService.GetNavigationService(this);
ns.Navigate( Page );
}