I used a those article to create AttachedProperty of Webbrowser control, so I can bind the “BindableSource” to my public property “Path”. It is great and work well with one small disadvantage.
In my ViewModel constructor the property is set to:
Path = new Uri("http://microsoft.com");
When the page is loaded I navigate to another link and “WebBrowser.Source” property is changed but “WebBrowser.BindableSource” is not changed.
When I click a button which set again:
Path = new Uri("http://microsoft.com");
The “BindableSourcePropertyChanged” is not called because the property has those value.
I had an idea how to solve it but its not good, because slow the performance, It is to set Path to another URL and after that set it to real URL (http://microsoft.com).
Path = new Uri("http://google.com");
Path = new Uri("http://microsoft.com");
Can you give me please a better idea, how can I solved it.
Thanks in advanced.
I propose to synchronize BindableSource with Navigated event.
You coukld achieve this by exposing another one attached behaviour at your WebBrowserUtility class, that reacts on Navigated event like this:
public static readonly DependencyProperty ShouldHandleNavigatedProperty =
DependencyProperty.RegisterAttached(
"ShouldHandleNavigated",
typeof(Boolean),
typeof(WebBrowserUtility),
new UIPropertyMetadata(false, ShouldHandleNavigatedPropertyChanged));
public static Boolean GetShouldHandleNavigated(DependencyObject obj)
{
return (Boolean)obj.GetValue(BindableSourceProperty);
}
public static void SetShouldHandleNavigated(DependencyObject obj, Boolean value)
{
obj.SetValue(ShouldHandleNavigatedProperty, value);
}
public static void ShouldHandleNavigatedPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser != null)
{
if ((bool)e.NewValue)
{
browser.Navigated += new NavigatedEventHandler(Browser_Navigated);
}
else
{
browser.Navigated -= new NavigatedEventHandler(Browser_Navigated);
}
}
}
private static void Browser_Navigated(object sender, NavigationEventArgs e)
{
WebBrowser browser = sender as WebBrowser;
if (browser != null)
{
browser.SetValue(WebBrowserUtility.BindableSourceProperty, browser.Source.AbsoluteUri);
}
}
Usage in xaml:
<WebBrowser self:WebBrowserUtility.BindableSource="{Binding WebAddress}"
self:WebBrowserUtility.ShouldHandleNavigated="True"/>
P.S. I should admit that this implementation is rather dirty, because setting of BindableSource inside Navigated event handler forces one additional event fireing. But this code works, and you could consider about its improvement.
EDITED
public static class WebBrowserUtility
{
...
private const string _SkipSourceChange = "Skip";
public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser != null)
{
string uri = e.NewValue as string;
if (!_SkipSourceChange.Equals(browser.Tag))
{
browser.Source = uri != null ? new Uri(uri) : null;
}
}
}
private static void Browser_Navigated(object sender, NavigationEventArgs e)
{
WebBrowser browser = sender as WebBrowser;
if (browser != null)
{
if (WebBrowserUtility.GetBindableSource(browser) != e.Uri.ToString())
{
browser.Tag = _SkipSourceChange;
browser.SetValue(WebBrowserUtility.BindableSourceProperty, browser.Source.AbsoluteUri);
browser.Tag = null;
}
}
}
}
Related
I may have painted myself into a corner. I have a custom touch keyboard user control that uses static events to pass key presses to text boxes when they are in focus. I'm trying to make a dependency property for all text boxes to subscribe to my touch keyboard event handlers when they are in focus and unsubscribe when they lose focus.
My current problem is that the PropertyChangedCallback, IsTouchKeyboardTargetChanged never fires. I've read that this is because it is set in XAML and thus never "changes."
So far none of the other answers I have found quite apply to my pattern here. I'm not even sure that this is the right way to go about this, but it would be very convenient for me if this works. I'm following the example set my MahApps with their TextBoxHelper.
If anyone can help me resolve the PropertyChangedCallback issue, or if anyone has a better idea of how to accomplish subscribing/unsubscribing to static events when any and all text boxes fire focus events, I would be most grateful.
CS
public class TouchTextBoxHelper : DependencyObject
{
public static readonly DependencyProperty IsTouchKeyboardTargetProperty = DependencyProperty.Register("IsTouchKeyboardTarget", typeof(bool), typeof(TouchTextBoxHelper), new FrameworkPropertyMetadata(false, IsTouchKeyboardTargetChanged));
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetIsTouchKeyboardTargetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsTouchKeyboardTargetProperty);
}
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static void SetIsTouchKeyboardTargetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsTouchKeyboardTargetProperty, value);
}
private static void IsTouchKeyboardTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tb = d as TextBox;
if (null == tb)
{
throw new InvalidOperationException("The property 'IsTouchKeyboardTarget' may only be set on TextBox elements.");
}
if (e.OldValue != e.NewValue)
{
//tb.SetValue(SpellCheck.IsEnabledProperty, (bool)e.NewValue);
if ((bool)e.NewValue)
{
tb.GotFocus += TextBoxBaseGotFocus;
tb.LostFocus += TextBoxBaseLostFocus;
}
else
{
tb.GotFocus -= TextBoxBaseGotFocus;
tb.LostFocus -= TextBoxBaseLostFocus;
}
}
}
private static void TextBoxBaseGotFocus(object sender, RoutedEventArgs e)
{
TouchTextBoxEvents.tb = sender as TextBox;
StaticEvents.OnShowTouchKeyboard(sender, e);
StaticEvents.TouchKeyboardKeyTouch += TouchTextBoxEvents.StaticEvents_TouchKeyboardKeyTouch;
StaticEvents.TouchKeyboardSpaceTouch += TouchTextBoxEvents.StaticEvents_TouchKeyboardSpaceTouch;
StaticEvents.TouchKeyboardBackspaceTouch += TouchTextBoxEvents.StaticEvents_TouchKeyboardBackspaceTouch;
}
private static void TextBoxBaseLostFocus(object sender, RoutedEventArgs e)
{
StaticEvents.OnHideTouchKeyboard(sender, e);
StaticEvents.TouchKeyboardKeyTouch -= TouchTextBoxEvents.StaticEvents_TouchKeyboardKeyTouch;
StaticEvents.TouchKeyboardSpaceTouch -= TouchTextBoxEvents.StaticEvents_TouchKeyboardSpaceTouch;
StaticEvents.TouchKeyboardBackspaceTouch -= TouchTextBoxEvents.StaticEvents_TouchKeyboardBackspaceTouch;
}
}
public class TouchTextBoxEvents
{
public static TextBox tb = null;
public static void StaticEvents_TouchKeyboardKeyTouch(object sender, EventArgs e)
{
int i = tb.CaretIndex;
string t = (sender as Button).Content.ToString();
tb.Text = tb.Text.Insert(tb.CaretIndex, t);
tb.CaretIndex = i + 1;
}
public static void StaticEvents_TouchKeyboardBackspaceTouch(object sender, EventArgs e)
{
int i = tb.CaretIndex;
if (tb.CaretIndex == 0) return;
tb.Text = tb.Text.Remove(tb.CaretIndex - 1, 1);
tb.CaretIndex = i - 1;
}
public static void StaticEvents_TouchKeyboardSpaceTouch(object sender, EventArgs e)
{
int i = tb.CaretIndex;
tb.Text = tb.Text.Insert(tb.CaretIndex, " ");
tb.CaretIndex = i + 1;
}
}
Edit:
It turns out all I had to do was update my setter:
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static void SetIsTouchKeyboardTargetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsTouchKeyboardTargetProperty, value);
TextBox tb = obj as TextBox;
if (tb == null) return;
if (value)
{
tb.GotFocus += TextBoxBaseGotFocus;
tb.LostFocus += TextBoxBaseLostFocus;
}
else
{
tb.GotFocus -= TextBoxBaseGotFocus;
tb.LostFocus -= TextBoxBaseLostFocus;
}
}
Edit2:
Putting the PropertyChangedCallback logic in the setter is a just bad. Fortunately, Clemens provided some great advice to use RegisterAttached instead of Register. This removed the need to derive from DependencyObject and cleared up the issue with the PropertyChangedCallback not firing.
An attached property must be registered by calling DependencyProperty.RegisterAttached instead of Register. There is also no need to derive the declaring class from DependencyObject:
public class TouchTextBoxHelper
{
public static readonly DependencyProperty IsTouchKeyboardTargetProperty =
DependencyProperty.RegisterAttached(
"IsTouchKeyboardTarget",
typeof(bool),
typeof(TouchTextBoxHelper),
new FrameworkPropertyMetadata(false, IsTouchKeyboardTargetChanged));
...
}
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:
public delegate void ValidationDelegate(bool isValid);
public class MyViewModel : ViewModelBase
{
public ValidationDelegate ValidationHandler { get { return (isValid) => OnValidate(isValid); } }
private void OnValidate(bool isValid)
{
// handle the validation event here
}
}
Now all you need is a behavior with an attached property that you can bind to this handler:
public class ValidateBehavior : Behavior<TextBox>
{
public ValidationDelegate Validated
{
get { return (ValidationDelegate)GetValue(ValidatedProperty); }
set { SetValue(ValidatedProperty, value); }
}
public static readonly DependencyProperty ValidatedProperty =
DependencyProperty.Register("Validated", typeof(ValidationDelegate), typeof(ValidateBehavior), new PropertyMetadata(null));
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged += ValidateText;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -= ValidateText;
}
private void ValidateText(object sender, TextChangedEventArgs e)
{
if (this.Validated != null)
{
bool isValid = true; // do text validation here
this.Validated(isValid);
}
}
}
And then finally add the behaviour to the TextBox in question and bind the handler:
<TextBox>
<i:Interaction.Behaviors>
<behaviors:ValidateBehavior Validated="{Binding ValidationHandler}"/>
</i:Interaction.Behaviors>
</TextBox>
EDIT: If you don't want to use a Blend behaviour then you can also do it with an attached behaviour:
public static class ValidateBehavior
{
public static ValidationDelegate GetValidate(TextBox textbox)
{
return (ValidationDelegate)textbox.GetValue(ValidateProperty);
}
public static void SetValidate(TextBox textbox, ValidationDelegate value)
{
textbox.SetValue(ValidateProperty, value);
}
public static readonly DependencyProperty ValidateProperty =
DependencyProperty.RegisterAttached(
"Validate",
typeof(ValidationDelegate),
typeof(ValidateBehavior),
new UIPropertyMetadata(null, OnValidateChanged));
static void OnValidateChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var textbox = depObj as TextBox;
if (textbox == null)
return;
if (e.OldValue is ValidationDelegate)
textbox.TextChanged -= OnTextChanged;
if (e.NewValue is ValidationDelegate)
textbox.TextChanged += OnTextChanged;
}
static void OnTextChanged(object sender, RoutedEventArgs e)
{
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
var textbox = e.OriginalSource as TextBox;
if (textbox != null)
{
var validate = GetValidate(textbox);
if (validate != null)
{
bool isValid = true; // do text validation here
validate(isValid);
}
}
}
}
And the corresponding XAML:
<TextBox behaviors:ValidateBehavior.Validate="{Binding ValidationHandler}" />
I am struggling with a dependencyproperty in a control. My dependencyproperty is an object which looks like this:
public class ChartGroupCollection : ObservableCollection<ChartGroup>, INotifyCollectionChanged
{
public void ClearDirty()
{
foreach (var grp in base.Items)
{
foreach(var run in grp.ChartRuns.Where(x=>x.IsDirty))
{
run.IsDirty = false;
}
grp.IsDirty = false;
}
}
[XmlIgnore]
public bool IsDirty //dirty flag for save prompt
{
get
{
....
}
}
}
[Serializable]
public class ChartGroup : INotifyPropertyChanged
{ ... //various properties }
The DependencyProperty is set up as here (named Tree, which is instance of a ChartGroupCollection):
public static readonly DependencyProperty TreeProperty = DependencyProperty.Register("Tree", typeof(ChartGroupCollection), typeof(ChartsControl), new PropertyMetadata(OnTreeChanged));
public ChartGroupCollection Tree
{
get { return (ChartGroupCollection)GetValue(TreeProperty); }
set { SetValue(TreeProperty, value); }
}
private static void OnTreeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var Treee = sender as ChartsControl;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= Tree_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<ChartGroup>)e.NewValue;
coll.CollectionChanged += Tree_CollectionChanged;
}
}
private static void Tree_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
(sender as ChartsControl).OnTreeChanged();
}
void OnTreeChanged()
{
MessageBox.Show("Do something..."); //RefreshCharts();
}
I seem to be getting to the OnTreeChanged event only at creation of the object, but once i do other work (adding to lists inside ChartGroup or changing properties of ChartGroup objects or even deleting elements of the observablecollection it never seems to trigger a refresh event. I have tried several other methods of getting the dependencyproperty found online but no solution worked for me. I wonder if it is down to the intrinsic nature of my dependencyproperty object or an error from my side
The sender argument in your Tree_CollectionChanged handler is not the ChartsControl instance, but the collection that raised the CollectionChanged event.
The Tree_CollectionChanged method should not be static
private void Tree_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnTreeChanged();
}
and it should be attached and removed like this:
private static void OnTreeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as ChartsControl;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.Tree_CollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.Tree_CollectionChanged;
}
}
Note also that adding a CollectionChanged handler does not care for subscribing to PropertyChanged events of the collection elements. You would also have to attach or remove a PropertyChanged handler whenever an element is added to or removed from the collection. Take a look at the NotifyCollectionChangedEventArgs.Action property.
I have a complex Plot RenderingControl that I have placed into a View. What would be the ideal way to handle zooming with respect to the MVVM pattern? I want the user to be able to zoom by clicking and dragging on the plot.
One approach I see would be to take the MouseMove, MouseUp, MouseDown events of the Plot control and wire them up to commands in the PlotViewModel. Now in response to the commands the ViewModel could update it's ZoomLevel property, which could be bound to the view, and cause the View to zoom in. While the user is clicking and dragging I would also like to display a rectangle indicating the region that will be zoomed. Would it make sense to keep an AnnotationViewModel in PlotViewModel for the zoom preview?
Another approach would be to handle it all in the View and not involve the ViewModel at all.
The main difference I see is that capturing the behavior in the ViewModel will make that behavior much more re-useable than in the View. Though I have a feeling that the underlying Plot control and the resulting View are complex enough that there isn't going to be much of chance for re-use anyway. What do you think?
I think there are several ways to solve your problem. HighCore right, when he says that Zoom applies to View, so it is advisable to leave it on the side View. But there are alternatives, we consider them below. Unfortunately, I did not deal with Plot RenderingControl, so I will describe a solution based on an abstract, independent of the control.
AttachedBehavior
In this case, I would have tried to identify possible all the work with the control via an attached behavior, it is ideally suited for the MVVM pattern, and it can be used in the Blend.
Example of work
In your View, control is defined and an attached behavior, like so:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True" ... />
And in code-behind:
public static class ZoomBehavior
{
public static readonly DependencyProperty IsStartProperty;
public static void SetIsStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsStartProperty, value);
}
public static bool GetIsStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsStartProperty);
}
static ZoomBehavior()
{
IsStartMoveProperty = DependencyProperty.RegisterAttached("IsStart",
typeof(bool),
typeof(ZoomBehavior),
new UIPropertyMetadata(false, IsStart));
}
private static void IsStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = sender as UIElement;
if (uiElement != null)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
uiElement.MouseDown += new MouseButtonEventHandler(ObjectMouseDown);
uiElement.MouseMove += new MouseEventHandler(ObjectMouseMove);
uiElement.MouseUp += new MouseButtonEventHandler(ObjectMouseUp);
}
}
}
// Below is event handlers
}
Once you're set to true for property IsStart, PropertyChanged handler is triggered and it set the handlers for events that contain the basic logic.
For the transmission of additional data in you behavior register additional dependency properties, for example:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True"
AttachedBehaviors:ZoomBehavior.ZoomValue="50" />
In code-behind:
// ... Here registered property
public static void SetZoomValue(DependencyObject DepObject, int value)
{
DepObject.SetValue(ZoomValueProperty, value);
}
public static int GetZoomValue(DependencyObject DepObject)
{
return (int)DepObject.GetValue(ZoomValueProperty);
}
// ... Somewhere in handler
int value = GetZoomValue(plotControl);
To retrieve data on the behavior, I use a singleton pattern. This pattern represents global static access point to the object and must guarantee the existence of a single instance of the class.
Example of using this pattern (taken from the behavior, who worked with the time display in the View):
public class TimeBehavior : INotifyPropertyChanged
{
// Global instance
private static TimeBehavior _instance = new TimeBehavior();
public static TimeBehavior Instance
{
get
{
return _instance;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private string _currentTime = DateTime.Now.ToString("HH:mm");
public string CurrentTime
{
get
{
return _currentTime;
}
set
{
if (_currentTime != value)
{
_currentTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentTime"));
}
}
}
}
private string _currentDayString = ReturnDayString();
public string CurrentDayString
{
get
{
return _currentDayString;
}
set
{
if (_currentDayString != value)
{
_currentDayString = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentDayString"));
}
}
}
}
private string _currentMonthAndDayNumber = ReturnMonthAndDayNumber();
public string CurrentMonthAndDayNumber
{
get
{
return _currentMonthAndDayNumber;
}
set
{
if (_currentMonthAndDayNumber != value)
{
_currentMonthAndDayNumber = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentMonthAndDayNumber"));
}
}
}
}
public static readonly DependencyProperty IsTimerStartProperty;
public static void SetIsTimerStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsTimerStartProperty, value);
}
public static bool GetIsTimerStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsTimerStartProperty);
}
static void OnIsTimerStartPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
}
static TimeBehavior()
{
IsTimerStartProperty = DependencyProperty.RegisterAttached("IsTimerStart",
typeof(bool),
typeof(TimeBehavior),
new PropertyMetadata(new PropertyChangedCallback(OnIsTimerStartPropertyChanged)));
}
private static void timer_Tick(object sender, EventArgs e)
{
_instance.CurrentTime = DateTime.Now.ToString("HH:mm");
_instance.CurrentDayString = ReturnDayString();
_instance.CurrentMonthAndDayNumber = ReturnMonthAndDayNumber();
}
}
Access to data in the View:
<TextBlock Name="WidgetTimeTextBlock"
Text="{Binding Path=CurrentTime,
Source={x:Static Member=AttachedBehaviors:TimeBehavior.Instance}}" />
Alternatives
Work in View via Interface
The point of this way is that we call a method in View via ViewModel, which does all the work, and he does not know about the View. This is accomplished by the operation of the interface and the well described here:
Talk to View
Using ServiceLocator
ServiceLocator allows you to work in the ViewModel, without violating the principles of MVVM. You have a RegisterService method where you register the instance of the service you want to provide and a GetService method which you would use to get the service you want.
More information can be found here:
Service Locator in MVVM
I am using the WPF WebBrowser control to display a PDF. I want to disable the context menu.
I have already tried the following -
Disable context menu in Internet Explorer control -
In the constructor, added the following -
webBrowser.LoadCompleted +=new LoadCompletedEventHandler(webBrowser_LoadCompleted);
// Event Handler
public void webBrowser_LoadCompleted(object sender, NavigationEventArgs e )
{
webBrowser.ContextMenu = null;
var t = webBrowser.Document as System.Windows.Forms.HtmlDocument;
// t is always coming as null, even though I can clearly see the pdf in the web browser control.
if(t != null)
{
t.ContextMenuShowing += new System.Windows.Forms.HtmlElementEventHandler(t_ContextMenuShowing);
}
}
Also checked http://social.msdn.microsoft.com/forums/en-US/wpf/thread/7c283faf-16c8-4b4e-a362-f292e3032abb/.
Used the approach to set the registry - HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Internet Explorer\Restrictions\NoBrowserContextMenu to DWORD 1.
When I set the regisrty, the PDF does not get displayed properly.
Also, since I am using PDF to display, I cannot set - in body -
oncontextmenu="return false;"
Cannot set IsWebBrowserContextMenuEnabled as I am using WPF web browser control.
It could be because of the PDF visualizer that wants to show it's own context menu.
Which context menu are you seeing? The one of IE or the one of the PDF visualizer?
I would also try to use the System.Windows.Forms.WebBrowser (you can use it in WPF). I used it in a WPF application because it has more functionality than the one of WPF.
You can wrap the Windows Forms WebBrowser to be bindable and disable the context menu (and access to other properties) in an easy way:
public class WindowsFormsWebBrowser : WindowsFormsHost
{
public static readonly DependencyProperty HtmlProperty =
DependencyProperty.Register(
"Html",
typeof(string),
typeof(WindowsFormsWebBrowser),
new PropertyMetadata(string.Empty, OnHtmlChanged, null));
public static readonly DependencyProperty IsContentMenuEnabledProperty =
DependencyProperty.Register(
"IsContentMenuEnabled",
typeof(bool),
typeof(WindowsFormsWebBrowser),
new PropertyMetadata(true, OnIsContextMenuEnabledChanged));
private readonly System.Windows.Forms.WebBrowser webBrowser = new System.Windows.Forms.WebBrowser();
public WindowsFormsWebBrowser()
{
Child = webBrowser;
}
public string Html
{
get { return GetValue(HtmlProperty) as string; }
set { SetValue(HtmlProperty, value); }
}
public bool IsContentMenuEnabled
{
get { return (bool)GetValue(IsContentMenuEnabledProperty); }
set { SetValue(IsContentMenuEnabledProperty, value); }
}
private static void OnHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var browser = d as WindowsFormsWebBrowser;
if (browser == null)
{
return;
}
browser.webBrowser.DocumentText = (string)e.NewValue;
}
private static void OnIsContextMenuEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var browser = d as WindowsFormsWebBrowser;
if (browser == null)
{
return;
}
browser.webBrowser.IsWebBrowserContextMenuEnabled = (bool)e.NewValue;
}
}
Here the steps to reference Windows Forms controls in a WPF project.
Here is a solution:
private void WebBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
((WebBrowser)sender).InvokeScript("eval",
"$(document).contextmenu(function() { return false; });");
}
Just use
// Disable the Context menu inside the web browser
webBrowser1.IsWebBrowserContextMenuEnabled = false;
i tried this in Windows Applications