Load a project resource into a WPF Webbrowser control - wpf

OK, so I thought this would be really simple even though I have only just got by previously when working with WPF project resource files.
When my application starts I want to load in a local (shipped with the application) html file into the Webbrowser control. However I cannot find an easy way to do this! Any ideas?

I just now ran into this same problem. I was hoping to simply do this:
<WebBrowser x:Name="myWebBrowser" Source="page.html"/>
But instead, I get this error:
Relative URIs are not allowed.
Parameter name: source
So that's annoying. Instead, I ended up with a solution very similar to yours in code behind:
myWebBrowser.Source = new Uri(new System.IO.FileInfo("page.html").FullName);
I'm sure there's some XAML contortionist kung-fu method with which to get around that issue, but I have no clue what it is. ^_^

Got round this in the end by setting the build action of the file to 'Copy Always' and then using Environment.CurrentDirectory to get the application directory:
string appDir = Environment.CurrentDirectory;
Uri pageUri = new Uri(appDir + "/page.html");
myWebBrowser.Source = pageUri;
That seems to work fine.

Late to the show, but I stumbled on this when I was looking for the same thing. Got it to work in a more WPF fashioned way.
The WebBrowser has a method NavigateToStream() so you can simply set the Resources Stream.
StreamResourceInfo info = Application.GetResourceStream(new Uri("Page.html", UriKind.Relative));
if (info != null)
myWebBrowser.NavigateToStream(info.Stream);
This way you can keep it a resource and you do not have to copy it to disk.

Set "Build Action" of the file to "Embedded Resource" and then use this:
webBrowserMain.NavigateToStream(System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream("PsHandler.Resources.gnu.html"));
Here is how it looks in project: http://i.stack.imgur.com/dGkpH.png

You may want to use the following scheme instead:
Change your resource's Build Action to Content.
Set Copy to Output Directory to Copy if newer.
Use the following URI scheme:
<WebBrowser Source="pack://siteoforigin:,,,/Resources/page1.html" />
As of .NET 4.5, this is working fine for me.

I wrapped this in a usercontrol to deal with some of the issues with the WebBrowser control..
Set the 'Build Action' property of the Html pages, and any embedded content to 'Content' and the 'Copy to Output Directory' to 'Copy if Newer'.
Maintain any page resource hierarchy in html just as you normally would on a server.
In the viewmodel, or from code-behind, you just set one of the HtmlUri, HtmlString or HtmlStream properties of this control (you can also turn off the annoying click sound ;o)..
Example Uri:
public Uri DefaultUri
{
get { return new Uri(#"pack://siteoforigin:,,,/Html/h1.html"); }
}
private Uri GetUri(string ItemName)
{
if (string.IsNullOrEmpty(ItemName)) return null;
return new Uri(#"pack://siteoforigin:,,,/Html/" + ItemName + ".html");
}
Use it like this:
<controls:SmartBrowser x:Name="wbData"
Grid.Row="0"
HtmlUri="{Binding HtmlUri}"
DefaultUri="{Binding DefaultUri}" />
SmartBrowser .xaml:
<Grid>
<WebBrowser Name="wbData" />
</Grid>
Control code-behind:
public partial class SmartBrowser : UserControl
{
#region Constants
private const int Feature = 21;
private const int SetFeatureOnProcess = 0x00000002;
#endregion
#region Api
[DllImport("urlmon.dll")]
[PreserveSig]
[return: MarshalAs(UnmanagedType.Error)]
static extern int CoInternetSetFeatureEnabled(int featureEntry, [MarshalAs(UnmanagedType.U4)] int dwFlags, bool fEnable);
#endregion
#region Fields
private WebBrowser _wbData;
#endregion
#region Constructor
public SmartBrowser()
{
InitializeComponent();
}
#endregion
#region Overrides
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_wbData = (WebBrowser)FindElement("wbData");
if (_wbData != null)
if (DefaultUri != null)
_wbData.Navigate(DefaultUri);
}
#endregion
#region Properties
#region DefaultUri
/// <summary>
/// Gets/Sets
/// </summary>
public static readonly DependencyProperty DefaultUriProperty =
DependencyProperty.Register("DefaultUri", typeof(Uri), typeof(SmartBrowser),
new PropertyMetadata(null, new PropertyChangedCallback(OnDefaultUriChanged)));
public Uri DefaultUri
{
get { return (Uri)GetValue(DefaultUriProperty); }
set { SetValue(DefaultUriProperty, value); }
}
private static void OnDefaultUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SmartBrowser sb = d as SmartBrowser;
if (e.NewValue == null) return;
if (sb._wbData == null) return;
Uri htmlUri = e.NewValue as Uri;
sb._wbData.Navigate(htmlUri);
}
#endregion
#region HtmlStream
/// <summary>
/// Gets/Sets
/// </summary>
public static readonly DependencyProperty HtmlStreamProperty =
DependencyProperty.Register("HtmlStream", typeof(Stream), typeof(SmartBrowser),
new PropertyMetadata(null, new PropertyChangedCallback(OnHtmlStreamChanged)));
public Stream HtmlStream
{
get { return (Stream)GetValue(HtmlStreamProperty); }
set { SetValue(HtmlStreamProperty, value); }
}
private static void OnHtmlStreamChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SmartBrowser sb = d as SmartBrowser;
if (e.NewValue == null) return;
if (sb._wbData == null) return;
Stream htmlStream = e.NewValue as Stream;
sb.Navigate(htmlStream);
}
#endregion
#region HtmlString
/// <summary>
/// Gets/Sets
/// </summary>
public static readonly DependencyProperty HtmlStringProperty =
DependencyProperty.Register("HtmlString", typeof(string), typeof(SmartBrowser),
new PropertyMetadata(null, new PropertyChangedCallback(OnHtmlStringChanged)));
public string HtmlString
{
get { return (string)GetValue(HtmlStringProperty); }
set { SetValue(HtmlStringProperty, value); }
}
private static void OnHtmlStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SmartBrowser sb = d as SmartBrowser;
if (e.NewValue == null) return;
if (sb._wbData == null) return;
string htmlString = e.NewValue as string;
sb.Navigate(htmlString);
}
#endregion
#region HtmlUri
/// <summary>
/// Gets/Sets
/// </summary>
public static readonly DependencyProperty HtmlUriProperty =
DependencyProperty.Register("HtmlUri", typeof(Uri), typeof(SmartBrowser),
new PropertyMetadata(null, new PropertyChangedCallback(OnHtmlUriChanged)));
public Uri HtmlUri
{
get { return (Uri)GetValue(HtmlUriProperty); }
set { SetValue(HtmlUriProperty, value); }
}
private static void OnHtmlUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SmartBrowser sb = d as SmartBrowser;
if (e.NewValue == null) return;
if (sb._wbData == null) return;
Uri htmlUri = e.NewValue as Uri;
sb.Navigate(htmlUri);
}
#endregion
#region SoundEffects
/// <summary>
/// Gets/Sets
/// </summary>
public static readonly DependencyProperty SoundEffectsProperty =
DependencyProperty.Register("SoundEffects", typeof(bool), typeof(SmartBrowser),
new PropertyMetadata(false, new PropertyChangedCallback(OnSoundEffectsChanged)));
public bool SoundEffects
{
get { return (bool)GetValue(SoundEffectsProperty); }
set { SetValue(SoundEffectsProperty, value); }
}
private static void OnSoundEffectsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SmartBrowser sb = d as SmartBrowser;
if (e.NewValue == null) return;
if (sb._wbData == null) return;
bool enable = (bool)e.NewValue;
sb.EnableSoundEffects(enable);
}
#endregion
#endregion
#region Methods
private void EnableSoundEffects(bool Enable)
{
try
{
CoInternetSetFeatureEnabled(Feature, SetFeatureOnProcess, !Enable);
}
catch { }
}
private void Navigate(Uri Html)
{
if (Html == null) return;
try
{
_wbData.Navigate(Html);
}
catch { }
}
private void Navigate(string Html)
{
if (string.IsNullOrEmpty(Html)) return;
try
{
_wbData.NavigateToString(Html);
}
catch { }
}
private void Navigate(Stream Html)
{
if (Html == null) return;
try
{
_wbData.NavigateToStream(Html);
}
catch { }
}
#endregion
#region Helpers
/// <summary>
/// Find an element in the visual tree
/// </summary>
/// <param name="name">Element name</param>
/// <returns>Element [object]</returns>
private object FindElement(string name)
{
try
{
if (this.Template != null)
return this.FindName(name);
}
catch { }
return null;
}
#endregion
}

Related

Catel and setting usercontrol's DataContext

I've tried to find a solution myself but I was not able, for some reason the DataContext is not correctly set up in the usercontrol's viewmodel
The idea is to have a single usercontrol that permits to perform a query on a fixed collection and allows the user to drop a treeviewitem that holds an item of the collection (in case the user have the treeview open)
In my main view I've defined :
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding PortfolioCompleteBox}" Height="25" LoadDefaultValue="True" />
Where PortfolioCompleteBox is a ViewModel defined in the MainViewModel as
public PortfolioChooserViewModel PortfolioCompleteBox
{
get { return GetValue<PortfolioChooserViewModel>(PortfolioChooserViewModelProperty); }
set { SetValue(PortfolioChooserViewModelProperty, value); }
}
public static readonly PropertyData PortfolioChooserViewModelProperty = RegisterProperty("PortfolioCompleteBox", typeof(PortfolioChooserViewModel));
public MainViewModel(ICreditLimitRepository creditLimitRepository, IDynamicContainer dynamicContainer)
{
this.creditLimitRepository = creditLimitRepository;
this.dynamicContainer = dynamicContainer;
LoadCreditLimitsCommand = new Command<object>(OnLoadCreditLimitsExecute, (() => OnLoadCreditLimitsCanExecute));
var viewModelFactory = this.GetServiceLocator().ResolveType<IViewModelFactory>();
PortfolioCompleteBox = viewModelFactory.CreateViewModel<PortfolioChooserViewModel>(null);
Model = new FiltersLoadModel();
}
My problem is that on the PortFolioChooserView I've the DataContext set to null (and I got 2 calls to the PortFolioChooserViewModel, one from the MainViewModel and the other one from the PortFolioChooserView's viewmodel locator)
public partial class PortfolioChooserView
{
private PortfolioChooserViewModel viewModel;
readonly bool isFirstLoad = true;
/// <summary>
/// Initializes a new instance of the <see cref="PortfolioChooserView"/> class.
/// </summary>
///
public PortfolioChooserView()
{
InitializeComponent();
if (isFirstLoad)
{
PortfolioCompleteBox.AllowDrop = true;
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
isFirstLoad = false;
this.Loaded += PortfolioChooserView_Loaded;
this.DataContextChanged += PortfolioChooserView_DataContextChanged;
}
}
void PortfolioChooserView_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
int t = 0;
}
void PortfolioChooserView_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
viewModel = (PortfolioChooserViewModel)this.DataContext;
}
private void OnElementDragOver(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var options = Telerik.Windows.DragDrop.DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions;
if (options != null)
{
var visual = options.DragVisual as TreeViewDragVisual;
if (visual != null) visual.IsDropPossible = true;
}
e.Handled = true;
}
private void OnElementDrop(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var context = ((IPortfolioAutoComplete)this.DataContext);
context.SetPortfolioAutoCompleteBox(e);
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
}
What am I doing wrong?
Thanks
Don't try to manage your own vm's
Catel will automatically accept a parent-vm as it's own vm as long as they are compatible. You don't need to handle this manually in your view loading in the view.
Instead of creating a VM in the parent VM, use a model only (so the vm only cares about what the VM itself should do). Then set the DC of the PortfolioChooserView to the model. Then the vm of the child view can accept the model in the ctor and be managed on it's own.
There are much better ways to communicate between vm's then trying to micro-manage like you are doing now. As always, see the docs.

How to bind a RelayCommand(MVVM) to a RoutedCommand? (CommandBinding)

I want to create a custom class of a CommandBinding, where a RelayCommand of my ViewModel is executed when the RoutedCommand is executed.
Currently there is only the possibility to create CommandBindings which have Executed methods in the codebehind class.
Example:
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandHandler"
CanExecute="CanExecuteHandler"/>
This needs the CloseCommandHandler methode in the code behind.
I would want to write the following.
<CommandBinding RoutedCommand="ApplicationCommands.Close" Command={Binding Path=CloseCommand}/>
The only problem is that i can't find the bubble down and up event of the RoutedCommands.
There is no
OnPreviewCommand(object command, object commandParammeter)
OnCommand(object command, object commandParammeter)
Where is the RoutedCommand bubble down and up handled?
I came up with a solutions of my own. Its not the most beautiful, but its working.
I derived from the ContentControl. The new Control has a RoutedCommandBindings property, which contains a list of "sort of" CommandBindings between RoutedCommands and RelayCommands.
It can be used like this.
<CSControls:RoutedCommandBinder>
<CSControls:RoutedCommandBinder.RoutedCommandBindings>
<CSControls:RoutedCommandBindingCollection>
<CSControls:RoutedCommandBinding RoutedCommand="{x:Static ApplicationCommands.New}" Command="{Binding Path=AddInstanceCommand}"/>
</CSControls:RoutedCommandBindingCollection>
</CSControls:RoutedCommandBinder.RoutedCommandBindings>
<CSControls:RoutedCommandBinder.Content>
<!-- Every RoutedCommand of type ApplicationCommands.New will execute the binded RelayCommand "AddInstanceCommand-->
</CSControls:RoutedCommandBinder.Content>
</CSControls:RoutedCommandBinder>
Here is the CustomControl code.
public class RoutedCommandBinder : ContentControl
{
public RoutedCommandBinder()
{
this.DataContextChanged += RoutedCommandBinder_DataContextChanged;
}
public static readonly DependencyProperty RoutedCommandBindingsProperty = DependencyProperty.Register("RoutedCommandBindings", typeof(RoutedCommandBindingCollection), typeof(RoutedCommandBinder), new PropertyMetadata(new RoutedCommandBindingCollection(), OnRoutedCommandBindingsChanged));
public RoutedCommandBindingCollection RoutedCommandBindings
{
get { return (RoutedCommandBindingCollection)this.GetValue(RoutedCommandBindingsProperty); }
set { SetValue(RoutedCommandBindingsProperty, value); }
}
private static void OnRoutedCommandBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RoutedCommandBinder binder = (RoutedCommandBinder)d;
binder.CommandBindings.Clear();
SetDataContextForCommandBindings(binder);
if (e.NewValue != null)
{
RoutedCommandBindingCollection bindings = (RoutedCommandBindingCollection)e.NewValue;
foreach (RoutedCommandBinding binding in bindings)
{
binder.CommandBindings.Add(new CommandBinding(binding.RoutedCommand, binder.RoutedCommandExecuted, binder.CanExecuteRoutedEventHandler));
}
}
}
private void RoutedCommandBinder_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetDataContextForCommandBindings(this);
}
private static void SetDataContextForCommandBindings(RoutedCommandBinder binder)
{
if (binder.DataContext != null && binder.RoutedCommandBindings != null)
{
foreach (RoutedCommandBinding binding in binder.RoutedCommandBindings)
{
binding.DataContext = binder.DataContext;
}
}
}
private void RoutedCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
RoutedCommandBinding binding = this.RoutedCommandBindings.FirstOrDefault(t => t.RoutedCommand == e.Command);
if (binding != null)
{
binding.Command.Execute(e.Parameter);
}
}
private void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)
{
RoutedCommandBinding binding = this.RoutedCommandBindings.FirstOrDefault(t => t.RoutedCommand == e.Command);
if (binding != null)
{
e.CanExecute = binding.Command.CanExecute(e.Parameter);
}
}
}
public class RoutedCommandBindingCollection : List<RoutedCommandBinding>
{
}
public class RoutedCommandBinding : FrameworkElement
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(RoutedCommandBinding), new PropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)this.GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty RoutedCommandProperty = DependencyProperty.Register("RoutedCommand", typeof(RoutedCommand), typeof(RoutedCommandBinding), new PropertyMetadata(null));
public RoutedCommand RoutedCommand
{
get { return (RoutedCommand)this.GetValue(RoutedCommandProperty); }
set { SetValue(RoutedCommandProperty, value); }
}
}

How can I fix the DependencyPropertyDescriptor AddValueChanged Memory Leak on AttachedBehavior?

I know I need to call RemoveValueChanged, but I have not been able to find a reliable place to call this. I'm learning that there probably isn't one.
I looks like I need to find a different way to monitor the change then adding a handler using AddValueChanged. I'm looking for advice on the best way to achieve this. I've seen the recommendation of using a PropertyChangedCallback in the PropertyMetadata, but I'm not sure how to do this when my TextBox and Adorner are not static. Also, the IsFocused property is not a DependencyProperty created in my class.
public sealed class WatermarkTextBoxBehavior
{
private readonly TextBox m_TextBox;
private TextBlockAdorner m_TextBlockAdorner;
private WatermarkTextBoxBehavior(TextBox textBox)
{
if (textBox == null)
throw new ArgumentNullException("textBox");
m_TextBox = textBox;
}
#region Behavior Internals
private static WatermarkTextBoxBehavior GetWatermarkTextBoxBehavior(DependencyObject obj)
{
return (WatermarkTextBoxBehavior)obj.GetValue(WatermarkTextBoxBehaviorProperty);
}
private static void SetWatermarkTextBoxBehavior(DependencyObject obj, WatermarkTextBoxBehavior value)
{
obj.SetValue(WatermarkTextBoxBehaviorProperty, value);
}
private static readonly DependencyProperty WatermarkTextBoxBehaviorProperty =
DependencyProperty.RegisterAttached("WatermarkTextBoxBehavior",
typeof(WatermarkTextBoxBehavior), typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(null));
public static bool GetEnableWatermark(TextBox obj)
{
return (bool)obj.GetValue(EnableWatermarkProperty);
}
public static void SetEnableWatermark(TextBox obj, bool value)
{
obj.SetValue(EnableWatermarkProperty, value);
}
public static readonly DependencyProperty EnableWatermarkProperty =
DependencyProperty.RegisterAttached("EnableWatermark", typeof(bool),
typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(false, OnEnableWatermarkChanged));
private static void OnEnableWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
var enabled = (bool)e.OldValue;
if (enabled)
{
var textBox = (TextBox)d;
var behavior = GetWatermarkTextBoxBehavior(textBox);
behavior.Detach();
SetWatermarkTextBoxBehavior(textBox, null);
}
}
if (e.NewValue != null)
{
var enabled = (bool)e.NewValue;
if (enabled)
{
var textBox = (TextBox)d;
var behavior = new WatermarkTextBoxBehavior(textBox);
behavior.Attach();
SetWatermarkTextBoxBehavior(textBox, behavior);
}
}
}
private void Attach()
{
m_TextBox.Loaded += TextBoxLoaded;
m_TextBox.TextChanged += TextBoxTextChanged;
m_TextBox.DragEnter += TextBoxDragEnter;
m_TextBox.DragLeave += TextBoxDragLeave;
m_TextBox.IsVisibleChanged += TextBoxIsVisibleChanged;
}
private void Detach()
{
m_TextBox.Loaded -= TextBoxLoaded;
m_TextBox.TextChanged -= TextBoxTextChanged;
m_TextBox.DragEnter -= TextBoxDragEnter;
m_TextBox.DragLeave -= TextBoxDragLeave;
m_TextBox.IsVisibleChanged -= TextBoxIsVisibleChanged;
}
private void TextBoxDragLeave(object sender, DragEventArgs e)
{
UpdateAdorner();
}
private void TextBoxDragEnter(object sender, DragEventArgs e)
{
m_TextBox.TryRemoveAdorners<TextBlockAdorner>();
}
private void TextBoxIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
UpdateAdorner();
}
private void TextBoxTextChanged(object sender, TextChangedEventArgs e)
{
var hasText = !string.IsNullOrEmpty(m_TextBox.Text);
SetHasText(m_TextBox, hasText);
}
private void TextBoxLoaded(object sender, RoutedEventArgs e)
{
Init();
}
#endregion
#region Attached Properties
public static string GetLabel(TextBox obj)
{
return (string)obj.GetValue(LabelProperty);
}
public static void SetLabel(TextBox obj, string value)
{
obj.SetValue(LabelProperty, value);
}
public static readonly DependencyProperty LabelProperty =
DependencyProperty.RegisterAttached("Label", typeof(string), typeof(WatermarkTextBoxBehavior));
public static Style GetLabelStyle(TextBox obj)
{
return (Style)obj.GetValue(LabelStyleProperty);
}
public static void SetLabelStyle(TextBox obj, Style value)
{
obj.SetValue(LabelStyleProperty, value);
}
public static readonly DependencyProperty LabelStyleProperty =
DependencyProperty.RegisterAttached("LabelStyle", typeof(Style),
typeof(WatermarkTextBoxBehavior));
public static bool GetHasText(TextBox obj)
{
return (bool)obj.GetValue(HasTextProperty);
}
private static void SetHasText(TextBox obj, bool value)
{
obj.SetValue(HasTextPropertyKey, value);
}
private static readonly DependencyPropertyKey HasTextPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("HasText", typeof(bool),
typeof(WatermarkTextBoxBehavior), new UIPropertyMetadata(false));
public static readonly DependencyProperty HasTextProperty =
HasTextPropertyKey.DependencyProperty;
#endregion
private void Init()
{
m_TextBlockAdorner = new TextBlockAdorner(m_TextBox, GetLabel(m_TextBox), GetLabelStyle(m_TextBox));
UpdateAdorner();
DependencyPropertyDescriptor focusProp = DependencyPropertyDescriptor.FromProperty(UIElement.IsFocusedProperty, typeof(FrameworkElement));
if (focusProp != null)
{
focusProp.AddValueChanged(m_TextBox, (sender, args) => UpdateAdorner());
}
DependencyPropertyDescriptor containsTextProp = DependencyPropertyDescriptor.FromProperty(HasTextProperty, typeof(TextBox));
if (containsTextProp != null)
{
containsTextProp.AddValueChanged(m_TextBox, (sender, args) => UpdateAdorner());
}
}
private void UpdateAdorner()
{
if (GetHasText(m_TextBox) ||
m_TextBox.IsFocused ||
!m_TextBox.IsVisible)
{
// Hide the Watermark Label if the adorner layer is visible
m_TextBox.ToolTip = GetLabel(m_TextBox);
m_TextBox.TryRemoveAdorners<TextBlockAdorner>();
}
else
{
// Show the Watermark Label if the adorner layer is visible
m_TextBox.ToolTip = null;
m_TextBox.TryAddAdorner<TextBlockAdorner>(m_TextBlockAdorner);
}
}
}
AddValueChanged of dependency property descriptor results in memory leak as you already know. So, as described here, you can create custom class PropertyChangeNotifier to listen to any dependency property changes.
Complete implementation can be found here - PropertyDescriptor AddValueChanged Alternative.
Quote from the link:
This class takes advantage of the fact that bindings use weak
references to manage associations so the class will not root the
object who property changes it is watching. It also uses a
WeakReference to maintain a reference to the object whose property it
is watching without rooting that object. In this way, you can maintain
a collection of these objects so that you can unhook the property
change later without worrying about that collection rooting the object
whose values you are watching.
Also for sake of completeness of answer I am posting complete code here to avoid any rot issue in future.
public sealed class PropertyChangeNotifier : DependencyObject, IDisposable
{
#region Member Variables
private readonly WeakReference _propertySource;
#endregion // Member Variables
#region Constructor
public PropertyChangeNotifier(DependencyObject propertySource, string path)
: this(propertySource, new PropertyPath(path))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, DependencyProperty property)
: this(propertySource, new PropertyPath(property))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, PropertyPath property)
{
if (null == propertySource)
throw new ArgumentNullException("propertySource");
if (null == property)
throw new ArgumentNullException("property");
_propertySource = new WeakReference(propertySource);
Binding binding = new Binding
{
Path = property,
Mode = BindingMode.OneWay,
Source = propertySource
};
BindingOperations.SetBinding(this, ValueProperty, binding);
}
#endregion // Constructor
#region PropertySource
public DependencyObject PropertySource
{
get
{
try
{
// note, it is possible that accessing the target property
// will result in an exception so i’ve wrapped this check
// in a try catch
return _propertySource.IsAlive
? _propertySource.Target as DependencyObject
: null;
}
catch
{
return null;
}
}
}
#endregion // PropertySource
#region Value
/// <summary>
/// Identifies the <see cref="Value"/> dependency property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
typeof(object), typeof(PropertyChangeNotifier), new FrameworkPropertyMetadata(null, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PropertyChangeNotifier notifier = (PropertyChangeNotifier)d;
if (null != notifier.ValueChanged)
notifier.ValueChanged(notifier, EventArgs.Empty);
}
/// <summary>
/// Returns/sets the value of the property
/// </summary>
/// <seealso cref="ValueProperty"/>
[Description("Returns/sets the value of the property")]
[Category("Behavior")]
[Bindable(true)]
public object Value
{
get
{
return GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
#endregion //Value
#region Events
public event EventHandler ValueChanged;
#endregion // Events
#region IDisposable Members
public void Dispose()
{
BindingOperations.ClearBinding(this, ValueProperty);
}
#endregion
}
A more lightweight solution for FrameworkElements and FrameworkContentElements is to subscribe to the Unloaded event and remove the handler. This requires a non-anonymous delegate (UpdateAdorner in that case) though:
focusProp.AddValueChanged(m_TextBox, UpdateAdorner);
m_TextBox.Unloaded += (sender, args) => focusProp.RemoveValueChanged(sender, UpdateAdorner);

wpf twoway bound DependencyProperty setcurrentvalue not working

I'm working on a custom behavior for the visiblox chart. This custom behavior has a dependency property Value that identifies the position of a cursor that consists of vertical line draw in the chart. This cursor follows the mouse if I set the property FollowMouse to true.
If I bind the Value property the changedvaluecallback only gets 0 as the newValue, while if the value is not bound it works properly. But if i change the source property of the binding (property on ViewModel) it works too. So the problem is setting the value with SetCurrentValue on PointerMoved.
Here is the source code of the behavior:
public class TimeCursorBehavior : BehaviourWithAxesBase
{
private System.Windows.Shapes.Line _line;
public TimeCursorBehavior()
: base("TimeCursor")
{
_line = new System.Windows.Shapes.Line();
_line.Stroke = System.Windows.Media.Brushes.Black;
_line.StrokeThickness = 2;
}
public override void DeInit()
{
base.DeInit();
Chart.BehaviourCanvas.Children.Remove(_line);
}
protected override void Init()
{
base.Init();
Chart.BehaviourCanvas.Children.Add(_line);
}
public override void PointerMoved(IBehaviourEventSource sender, PointerEventContext context)
{
base.PointerMoved(sender, context);
if (!FollowMouse)
return;
IComparable xDataValue = XAxis.GetRenderPositionAsDataValueWithZoom(context.Point.X);
SetCurrentValue(ValueProperty, xDataValue);
}
public override void BehaviourCanvasSizeChanged(IBehaviourEventSource sender, SizeChangedEventArgs e)
{
base.BehaviourCanvasSizeChanged(sender, e);
_line.Y2 = e.NewSize.Height;
}
#region Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(IComparable), typeof(TimeCursorBehavior), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
(sender as TimeCursorBehavior).OnValueChanged(args.OldValue as IComparable, args.NewValue as IComparable);
}
private void OnValueChanged(IComparable oldValue, IComparable newValue)
{
if (XAxis == null)
return;
double x = XAxis.GetDataValueAsRenderPositionWithZoom(newValue);
_line.X1 = x;
_line.X2 = x;
}
public IComparable Value
{
get
{
return GetValue(ValueProperty) as IComparable;
}
set
{
SetValue(ValueProperty, value);
}
}
#endregion
#region FollowMouse
public static readonly DependencyProperty FollowMouseProperty = DependencyProperty.Register("FollowMouse", typeof(bool), typeof(TimeCursorBehavior), new PropertyMetadata(false));
public bool FollowMouse
{
get
{
return (bool)GetValue(FollowMouseProperty);
}
set
{
SetValue(FollowMouseProperty, value);
}
}
#endregion
}
Does anyone know why setcurrentvalue is not updating the value accordingly?
Found the problem.
My property in the ViewModel is an decimal, and the property returned by the line below is a double.
IComparable xDataValue = XAxis.GetRenderPositionAsDataValueWithZoom(context.Point.X);
I added an converter to the binding it everything worked as expected.

How can I implement a commandable ColumnSeries in the WPF Toolkit's Chart Control

I need to be able to specify a command to run when the SelectionChanged event fires. I already know how to implement the ICommandSource interface; what I need to know is how I can just add a command to the column series to handle the SelectionChanged event.
When I inherit from the ColumnBarBaseSeries<...> base class I have to override GetAxes() and UpdateDatePoint() which I am not sure how to implement.
You can use attached behaviours to solve this problem.
Create a SelectionChangedBehaviour that wires a selectionChanged event to the element that you're attaching the behaviour to then you can binding any ICommand to that behaviour.
For more on attached behaviours -
Introduction article by Josh Smith
Overview of the concept
Another good introduction
Hope that helps
Here is some code to add an attached behavior to a ColumnSeries for a SelectionChanged Command.
public static class ColumnSeriesBehavior
{
private static DelegateCommand<object> SelectionChangedCommand;
public static DelegateCommand<object> GetSelectionChangedCommand(ColumnSeries cs)
{
return cs.GetValue(SelectionChangedCommandProperty) as DelegateCommand<object>;
}
public static void SetSelectionChangedCommand(ColumnSeries cs, DelegateCommand<object> value)
{
cs.SetValue(SelectionChangedCommandProperty, value);
}
// Using a DependencyProperty as the backing store for SelectionChangedCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(DelegateCommand<object>), typeof(ColumnSeriesBehavior), new UIPropertyMetadata(null, OnSelectionChangedCommandChanged));
private static void OnSelectionChangedCommandChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ColumnSeries item = depObj as ColumnSeries;
if (item == null)
{
return;
}
if (e.NewValue is DelegateCommand<object> == false)
{
return;
}
SelectionChangedCommand = e.NewValue as DelegateCommand<object>;
item.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(Column_SelectionChanged);
}
private static void Column_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (SelectionChangedCommand != null)
SelectionChangedCommand.Execute(sender);
}
}
And in the XAML to attach the property:
<chartingToolkit:Chart.Series>
<chartingToolkit:ColumnSeries
IsSelectionEnabled="True"
ItemsSource="{Binding YourItemSource}"
IndependentValueBinding="{Binding YourIndValue, Path=YourIndValuePath}"
DependentValueBinding="{Binding YourDepValue, Path=YourDepValuePath}"
>
<chartingToolkit:ColumnSeries.Style>
<Style>
<!-- Attaching the SelectionChangedCommand behavior -->
<Setter Property="local:ColumnSeriesBehavior.SelectionChangedCommand"
Value="{Binding YourDelegateCommand}"/>
</Style>
</chartingToolkit:ColumnSeries.Style>
</chartingToolkit:ColumnSeries>
</chartingToolkit:Chart.Series>
Here is some code that appears to work for me to implement your own CommandColumnSeries, I stole a lot of it from the source for the ColumnSeries Sealed Class:
public class CommandColumnSeries : ColumnBarBaseSeries<ColumnDataPoint>
{
#region "ICommandSource"
[Localizability(LocalizationCategory.NeverLocalize), Category("Action"), Bindable(true)]
public ICommand Command
{
get
{
return (ICommand)base.GetValue(CommandProperty);
}
set
{
base.SetValue(CommandProperty, value);
}
}
[Bindable(true), Category("Action"), Localizability(LocalizationCategory.NeverLocalize)]
public object CommandParameter
{
get
{
return base.GetValue(CommandParameterProperty);
}
set
{
base.SetValue(CommandParameterProperty, value);
}
}
[Category("Action"), Bindable(true)]
public IInputElement CommandTarget
{
get
{
return (IInputElement)base.GetValue(CommandTargetProperty);
}
set
{
base.SetValue(CommandTargetProperty, value);
}
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
#endregion
#region public IRangeAxis DependentRangeAxis
/// <summary>
/// Gets or sets the dependent range axis.
/// </summary>
public IRangeAxis DependentRangeAxis
{
get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; }
set { SetValue(DependentRangeAxisProperty, value); }
}
/// <summary>
/// Identifies the DependentRangeAxis dependency property.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because the base classes need to share this dependency property.")]
public static readonly DependencyProperty DependentRangeAxisProperty =
DependencyProperty.Register(
"DependentRangeAxis",
typeof(IRangeAxis),
typeof(ColumnSeries),
new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged));
/// <summary>
/// DependentRangeAxisProperty property changed handler.
/// </summary>
/// <param name="d">ColumnBarBaseSeries that changed its DependentRangeAxis.</param>
/// <param name="e">Event arguments.</param>
private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandColumnSeries source = (CommandColumnSeries)d;
IRangeAxis newValue = (IRangeAxis)e.NewValue;
source.OnDependentRangeAxisPropertyChanged(newValue);
}
/// <summary>
/// DependentRangeAxisProperty property changed handler.
/// </summary>
/// <param name="newValue">New value.</param>
private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue)
{
this.InternalDependentAxis = (IAxis)newValue;
}
#endregion public IRangeAxis DependentRangeAxis
#region public IAxis IndependentAxis
/// <summary>
/// Gets or sets the independent category axis.
/// </summary>
public IAxis IndependentAxis
{
get { return GetValue(IndependentAxisProperty) as IAxis; }
set { SetValue(IndependentAxisProperty, value); }
}
/// <summary>
/// Identifies the IndependentAxis dependency property.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because the base classes need to share this dependency property.")]
public static readonly DependencyProperty IndependentAxisProperty =
DependencyProperty.Register(
"IndependentAxis",
typeof(IAxis),
typeof(ColumnSeries),
new PropertyMetadata(null, OnIndependentAxisPropertyChanged));
/// <summary>
/// IndependentAxisProperty property changed handler.
/// </summary>
/// <param name="d">ColumnBarBaseSeries that changed its IndependentAxis.</param>
/// <param name="e">Event arguments.</param>
private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandColumnSeries source = (CommandColumnSeries)d;
IAxis newValue = (IAxis)e.NewValue;
source.OnIndependentAxisPropertyChanged(newValue);
}
/// <summary>
/// IndependentAxisProperty property changed handler.
/// </summary>
/// <param name="newValue">New value.</param>
private void OnIndependentAxisPropertyChanged(IAxis newValue)
{
this.InternalIndependentAxis = (IAxis)newValue;
}
#endregion public IAxis IndependentAxis
public CommandColumnSeries()
{
this.SelectionChanged += new SelectionChangedEventHandler(CommandColumnSeries_SelectionChanged);
}
private void CommandColumnSeries_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (Command != null)
{
RoutedCommand routedCommand = Command as RoutedCommand;
CommandParameter = e.Source;
if (routedCommand != null)
{
routedCommand.Execute(CommandParameter, CommandTarget);
}
else
{
Command.Execute(CommandParameter);
}
}
}
protected override void GetAxes(DataPoint firstDataPoint)
{
// Taken from the source of the ColumnSeries sealed class.
GetAxes(
firstDataPoint,
(axis) => axis.Orientation == AxisOrientation.X,
() => new CategoryAxis { Orientation = AxisOrientation.X },
(axis) =>
{
IRangeAxis rangeAxis = axis as IRangeAxis;
return rangeAxis != null && rangeAxis.Origin != null && axis.Orientation == AxisOrientation.Y;
},
() =>
{
IRangeAxis rangeAxis = CreateRangeAxisFromData(firstDataPoint.DependentValue);
rangeAxis.Orientation = AxisOrientation.Y;
if (rangeAxis == null || rangeAxis.Origin == null)
{
throw new InvalidOperationException("No Suitable Axes found for plotting range axis.");
}
DisplayAxis axis = rangeAxis as DisplayAxis;
if (axis != null)
{
axis.ShowGridLines = true;
}
return rangeAxis;
});
}
protected override void UpdateDataPoint(DataPoint dataPoint)
{
// This code taken from the ColumnSeries sealed class.
if (SeriesHost == null )//|| PlotArea == null)
{
return;
}
object category = dataPoint.ActualIndependentValue ?? (IndexOf<DataPoint>(this.ActiveDataPoints, dataPoint) + 1);
Range<UnitValue> coordinateRange = GetCategoryRange(category);
if (!coordinateRange.HasData)
{
return;
}
else if (coordinateRange.Maximum.Unit != Unit.Pixels || coordinateRange.Minimum.Unit != Unit.Pixels)
{
throw new InvalidOperationException("This Series Does Not Support Radial Axes");
}
double minimum = (double)coordinateRange.Minimum.Value;
double maximum = (double)coordinateRange.Maximum.Value;
double plotAreaHeight = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value.Value;
IEnumerable<CommandColumnSeries> columnSeries = SeriesHost.Series.OfType<CommandColumnSeries>().Where(series => series.ActualIndependentAxis == ActualIndependentAxis);
int numberOfSeries = columnSeries.Count();
double coordinateRangeWidth = (maximum - minimum);
double segmentWidth = coordinateRangeWidth * 0.8;
double columnWidth = segmentWidth / numberOfSeries;
int seriesIndex = IndexOf<CommandColumnSeries>(columnSeries, this);
double dataPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ToDouble(dataPoint.ActualDependentValue)).Value.Value;
double zeroPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Origin).Value.Value;
double offset = seriesIndex * Math.Round(columnWidth) + coordinateRangeWidth * 0.1;
double dataPointX = minimum + offset;
if (GetIsDataPointGrouped(category))
{
// Multiple DataPoints share this category; offset and overlap them appropriately
IGrouping<object, DataPoint> categoryGrouping = GetDataPointGroup(category);
int index = GroupIndexOf(categoryGrouping, dataPoint);
dataPointX += (index * (columnWidth * 0.2)) / (categoryGrouping.Count() - 1);
columnWidth *= 0.8;
Canvas.SetZIndex(dataPoint, -index);
}
if (CanGraph(dataPointY) && CanGraph(dataPointX) && CanGraph(zeroPointY))
{
double left = Math.Round(dataPointX);
double width = Math.Round(columnWidth);
double top = Math.Round(plotAreaHeight - Math.Max(dataPointY, zeroPointY) + 0.5);
double bottom = Math.Round(plotAreaHeight - Math.Min(dataPointY, zeroPointY) + 0.5);
double height = bottom - top + 1;
Canvas.SetLeft(dataPoint, left);
Canvas.SetTop(dataPoint, top);
dataPoint.Width = width;
dataPoint.Height = height;
}
}
private static int IndexOf<T>(IEnumerable<T> collection, T target)
{
int i = 0;
foreach (var obj in collection)
{
if (obj.Equals(target))
return i;
i++;
}
return -1;
}
private static int GroupIndexOf(IGrouping<object, DataPoint> group, DataPoint point)
{
int i = 0;
foreach (var pt in group)
{
if (pt == point)
return i;
i++;
}
return -1;
}
/// <summary>
/// Returns a value indicating whether this value can be graphed on a
/// linear axis.
/// </summary>
/// <param name="value">The value to evaluate.</param>
/// <returns>A value indicating whether this value can be graphed on a
/// linear axis.</returns>
private static bool CanGraph(double value)
{
return !double.IsNaN(value) && !double.IsNegativeInfinity(value) && !double.IsPositiveInfinity(value) && !double.IsInfinity(value);
}
/// <summary>
/// Converts an object into a double.
/// </summary>
/// <param name="value">The value to convert to a double.</param>
/// <returns>The converted double value.</returns>
private static double ToDouble(object value)
{
return Convert.ToDouble(value, CultureInfo.InvariantCulture);
}
}

Resources