I am using the Caliburn.Micro framework for building my application. I am trying to create a custom chart control derived from the WPF toolkit chart, which needs to have 2 custom dependency properties added to it.
For some reason, Caliburn.Micro is not binding correctly to the DP's that I created, but is working fine for existing ones. Is there something that I need to do for CM to recognize these additional properties?
(In my example, the binding Title="{Binding ChartSeriesType}" works correctly. The ChartData and ChartType are not being updated.)
SampleChart.xaml.cs
public partial class SampleChart : Chart
{
public ChartSeriesType ChartType
{
get { return (ChartSeriesType)GetValue(ChartTypeProperty); }
set
{
SetValue(ChartTypeProperty, value);
dataChaged();
}
}
// Using a DependencyProperty as the backing store for ChartType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ChartTypeProperty =
DependencyProperty.Register("ChartType", typeof(ChartSeriesType), typeof(SampleChart), new UIPropertyMetadata(null));
public AgilityTableBase ChartData
{
get { return (AgilityTableBase)GetValue(ChartDataProperty); }
set
{
SetValue(ChartDataProperty, value);
dataChaged();
}
}
// Using a DependencyProperty as the backing store for ChartData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ChartDataProperty =
DependencyProperty.Register("ChartData", typeof(AgilityTableBase), typeof(SampleChart), new UIPropertyMetadata(null));
private void dataChaged()
{
Console.WriteLine("data changed");
}
}
SampleChartView.xaml:
<UserControl x:Class="Agility.Presentation.ReportViewing.SampleChartView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="http://www.caliburnproject.org"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Agility.Presentation.ReportViewing"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="300"
d:DesignWidth="300" mc:Ignorable="d">
<local:SampleChart Title="{Binding ChartSeriesType}" ChartData="{Binding ReportTable}" ChartType="{Binding ChartSeriesType}" />
SampleChartViewModel.cs:
public class SampleChartViewModel : PropertyChangedBase
{
private ChartSeriesType _chartType;
private SampleTableBase _reportTable;
public SampleChartViewModel(SampleTableBase reportTable)
{
_reportTable = reportTable;
}
public SampleTableBase ReportTable
{
get { return _reportTable; }
set
{
_reportTable = value;
this.NotifyOfPropertyChange(() => ReportTable);
}
}
public ChartSeriesType ChartSeriesType
{
get { return _chartType; }
set
{
_chartType = value;
this.NotifyOfPropertyChange(() => ChartSeriesType);
}
}
}
EDIT
The correct way to register the ChartType DependencyProperty is:
// Using a DependencyProperty as the backing store for ChartType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ChartTypeProperty =
DependencyProperty.Register("ChartType", typeof(ChartSeriesType), typeof(AgilityChart), new UIPropertyMetadata(ChartSeriesType.Bar
, new PropertyChangedCallback(dataChanged)));
private static void dataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("data changed");
}
Now, the dataChanged() method is being called.
One mistake I see in your code is that you add dataChaged() call to your property setter. This setter is only a convenience for you. It wont be called by binding. To react to dependency property change set callback delegate in UIPropertyMetadata that you pass while registering dp.
Moreover first argument of UIPropertyMetadata constructor is default value of property and ChartSeriesType looks like an enum so null in not appropriate default.
Next thing is that the dp owner type is set to AgilityChart and should be SampleChart, because that is your class name.
Related
I have a behavior with a xaml interface of:
<i:Interaction.Behaviors>
<b:ChartCanvasItemBehavior
Source="{Binding MovieUri}"
Timeline="{Binding Path=Timeline, ElementName=ChartCanvasRightPanel, Mode=OneWayToSource}"
/>
</i:Interaction.Behaviors>
The behavior creates an adorner that does:
public double Timeline
{
get { return (double)GetValue(TimelineProperty); }
set { SetValue(TimelineProperty, value); }
}
// Using a DependencyProperty as the backing store for Timeline. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TimelineProperty =
DependencyProperty.Register("Timeline", typeof(double), typeof(MovieControlForChartCanvas), new UIPropertyMetadata(0.0));
To link the two in the behavior, I have:
var bindingMovieTime = new Binding
{
Source = mc,
Path = new PropertyPath(MovieControlForChartCanvas.TimelineProperty)
};
BindingOperations.SetBinding(this, iTimelineProperty, bindingMovieTime);
#region [Dependency Properties used to bind to the MovieControlForChartCanvas]
// These dependecy properties have to be DIFFERENT then the dependency properties used for the ChartCanvasItemBehavior xaml interface.
// Using the same dependecy property for both bindings to the MovieControl and the Xaml interface results in losing one of the first bindings (i.e., the xaml binding).
public double iTimeline
{
get { return (double)GetValue(iTimelineProperty); }
set { SetValue(iTimelineProperty, value); }
}
// Using a DependencyProperty as the backing store for iTimeline. This enables animation, styling, binding, etc...
public static readonly DependencyProperty iTimelineProperty =
DependencyProperty.Register("iTimeline", typeof(double), typeof(ChartCanvasItemBehavior), new PropertyMetadata(0.0,
(s, e) =>
{
ChartCanvasItemBehavior d = s as ChartCanvasItemBehavior;
d.Timeline = (double)e.NewValue;
}
));
#endregion
public double Timeline
{
get { return (double)GetValue(TimelineProperty); }
set { SetValue(TimelineProperty, value); }
}
// Using a DependencyProperty as the backing store for Timeline. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TimelineProperty =
DependencyProperty.Register("Timeline", typeof(double), typeof(ChartCanvasItemBehavior ), new PropertyMetadata(0.0));
This seems to require a second property between the behavior and the adorner. If I use one property for both the behavior interface and the behavior-to-adorner interface, the first assignment to that behavior, i.e, from the xaml, gets overwritten by the binding to the adorner.
Can this be done using only one dependency property in the behavior with multibinding?
If so, how?
To rephrase, is it possible to use one dependency property in the behavior to serve as an interface in both the xaml and to the adorner? (multibinding?)
Thanks for any suggestions.
WPF Data binding doesnt work for custom controls that are defined inside a xaml collection tag. I just want to define a collection of custom widgets inside a custom control and bind some widgets properties against viewmodel properties. Like so.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget ImportantToggle="{Binding SomeToggle}"/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</Grid>
</Window>
That is my custom control. I use an obseravblecollection for the widgets and call SetValue in the constructor to get propertychanged callback later (right now not used in example)
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
namespace WpfApp1
{
public class MyCustomControl : FrameworkElement
{
public ObservableCollection<MyCustomWidget> Widgets
{
get { return (ObservableCollection<MyCustomWidget>)this.GetValue(WidgetsProperty); }
set { this.SetValue(WidgetsProperty, value); }
}
public static DependencyProperty WidgetsProperty = DependencyProperty.Register("Widgets", typeof(ObservableCollection<MyCustomWidget>), typeof(MyCustomControl), new PropertyMetadata(null, (e, args) => ((MyCustomControl)e).WidgetsChanged(args)));
public void WidgetsChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("widgets collection object changed inside my custom control!");
}
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
}
}
}
and that is my custom widget:
namespace WpfApp1
{
public class MyCustomWidget : FrameworkContentElement
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty = DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget), new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
}
And finally my simplistic ViewModel:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp1
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _someToggle;
public bool SomeToggle
{
get { return this._someToggle; }
set
{
this._someToggle = value;
this.NotifyPropertyChanged();
}
}
public MainViewModel()
{
this.SomeToggle = !this.SomeToggle;
}
}
}
Thats the output I get from Debug.Writeline: widgets collection object changed inside my custom control!
Observation: I cant bind against properties of MyCustomWidget. I understand that the binding might fail in this scenario because the observablecollection is created inside of the constructor of mycustomcontrol, but I dont know how to fix it to get the binding working inside mycustomwidget.
For that binding to work, your local:MyCustomWidget needs to have the same DataContext as the main window. WPF elements inherit their logical parent's DataContext. MyCustomWidget doesn't, because it's not in the logical tree. It's just sitting there. You're not adding it to any kind of normal child collection of its parent, just to a random ObservableCollection that the framework doesn't know about.
The code below is probably a crude hack. I haven't investigated this corner of WPF. I urge you with the utmost sincerity to find out the right way of doing this. But with this addition to your code, I hit the propertychanged event in MyCustomWidget when the binding is initialized.
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
Widgets.CollectionChanged += Widgets_CollectionChanged;
}
private void Widgets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems is System.Collections.IEnumerable)
{
foreach (MyCustomWidget widget in e.NewItems)
{
AddLogicalChild(widget);
}
}
}
By the way, you can save the trouble of toggling the toggle in the MainViewModel constructor. That happens long before the binding exists. I added a checkbox instead:
<StackPanel>
<CheckBox IsChecked="{Binding SomeToggle}">Test Toggle</CheckBox>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</StackPanel>
Update:
This omits your Widgets collection entirely, and the binding works without any effort on our part. The child widgets will be in MyCustomControl.Children. Importantly that we aren't limiting the child type to MyCustomWidget any more. That's a significant design change, and may not fit your requirements. You could examine the Panel class closely, and write a class that works the same way, but accepts only one type of child (that would mean writing an analog of UIElementCollection, which will be mostly a big pile of tedious boilerplate).
MyCustomControl.cs
[ContentProperty("Children")]
public class MyCustomControl : Panel
{
}
MyCustomWidget.cs
public class MyCustomWidget : Control
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty =
DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget),
new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
MainWindow.xaml
<local:MyCustomControl>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl>
I have a dependency property in a class which inherits from Canvas, like so:
public partial class HueVisualizer : Canvas
{
public HueVisualizer()
{
InitializeComponent();
}
public decimal InnerHue
{
get { return (decimal)GetValue(HueProperty); }
set { SetValue(HueProperty, value); }
}
// Using a DependencyProperty as the backing store for InnerHue,Saturation and Luminance. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HueProperty =
DependencyProperty.Register("InnerHue", typeof(decimal), typeof(LuminanceVisualizer), new FrameworkPropertyMetadata((decimal)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
I'm trying to bind to it in Xaml like so:
<UserControl x:Class="Project1.UserControl1"
x:Name="TheControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:project1="clr-namespace:Project1"
mc:Ignorable="d"
d:DesignHeight="120" d:DesignWidth="300">
...
<Grid Grid.Row="0" x:Name="HueGrid">
<project1:HueVisualizer x:Name="HueVisual"
InnerHue ="{Binding ElementName=TheControl, Path=Hue, Mode=TwoWay}"
Height="20"
Width="{Binding ElementName=TheControl, Path=Width}"/>
</Grid>
<UserControl />
For completeness, the properties I'm trying to bind from:
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public decimal Hue
{
get { return (decimal)GetValue(HueProperty); }
set { SetValue(HueProperty, value); }
}
...
// Using a DependencyProperty as the backing store for Hue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HueProperty =
DependencyProperty.Register("Hue", typeof(decimal), typeof(UserControl1), new
FrameworkPropertyMetadata((decimal)0));
...
}
However, when I try to run/debug the project, I get an exception on InitializeComponent() of UserControl1:
A 'Binding' cannot be set on the 'InnerHue' property of type 'HueVisualizer'. A
'Binding' can only be set on a DependencyProperty of a DependencyObject.
No matter how many times I look at examples, it seems to me that InnerHue should be a valid dependency property. I also double checked to make sure that Canvas is a DependencyObject (if it weren't, GetValue and SetValue should throw a compiler error). What in the world am I doing incorrectly? As I am relatively new to WPF, I can't help but feel that I'm missing something obvious.
You gave the wrong owner type for your DependencyProperty
you wrote LuminanceVisualizer and it should be HueVisualizer .
public static readonly DependencyProperty HueProperty =
DependencyProperty.Register("InnerHue", typeof(decimal),
typeof(LuminanceVisualizer), // Replace with HueVisualizer
new FrameworkPropertyMetadata((decimal)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault))
Consider this attached property:
public static readonly DependencyProperty TestImageProperty =
DependencyProperty.RegisterAttached("TestImage",
typeof(BitmapSource), typeof(TestView), new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender));
public static void SetTestImage(UIElement element, BitmapSource value)
{
element.SetValue(TestImageProperty, value);
}
public static BitmapSource GetTestImage(UIElement element)
{
return (BitmapSource)element.GetValue(TestImageProperty);
}
public BitmapSource TestImage
{
get
{
return GetTestImage(this);
}
set
{
SetTestImage(this, value);
}
}
And the XAML:
<common:UserControlBase x:Class="MyViews.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MyViews"
xmlns:common="clr-namespace:MyCommon.Common;assembly=MyCommon.Common"
xmlns:mefed="clr-namespace:MEFedMVVM.ViewModelLocator;assembly=MEFedMVVM.WPF"
mefed:ViewModelLocator.SharedViewModel="MyViewModel"
views:TestView.TestImage="{Binding TestImage}"
Hence, I'm binding the view's TestImage property to the viewmodel's TestImage property! Then, from code-behind I update the property:
this.TestImage = image;
This does NOT trigger the setter in my viewmodel.
What is causing this?
Put the Binding on TwoWay Mode will resolve the problem.
I have a UserControl called ActionsTreeView I built using MVVM practices where I have an IPluginsProvider interface that populates the data in my UserControl. I want to be able to provide an object implementating this IContentProvider interface as a parameter to initialize my UserControl's ViewModel.
Here is my approach so far, which isn't working. I am wondering if I'm going down the right path? I declare a DependencyProperty in my user control which is visible to my mainWindow where I want to instantiate this UserControl. This code just attempts to pass the PluginsProvider object to my UserControl which needs it to build its ViewModel.
My PluginProvider DependencyProperty setter in my UserControl never gets hit because my My PropertyChanged handler is always null in MainWindow.xaml.cs I think I have the code right, but not sure I'm going down the right road and what I'm missing to make this connection?
ActionsTreeView.xaml.cs
public partial class ActionsTreeView: UserControl
{
public static readonly DependencyProperty PluginProviderProperty = DependencyProperty.Register("PluginProvider", typeof(Models.IPluginsProvider), typeof(ActionsTreeView), new FrameworkPropertyMetadata(null, OnPluginProviderChanged));
private ViewModels.ActionsTreeViewModel vm;
public ActionsTreeView()
{
//Wire-up our ViewModel with the data provider and bind it to DataContext for our user control
//This is a Mock-up until I figure out a way to get the real provider here
Models.IPluginProvider pluginSource = new Models.MockPluginProvider();
vm = new ViewModels.ActionsTreeViewModel(pluginSource );
this.DataContext = vm;
InitializeComponent();
}
private static void OnPluginProviderChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ActionsTreeView)source).PluginProvider = (Models.IPluginsProvider)e.NewValue;
}
public Models.IPluginsProvider PluginProvider
{
get
{
return (Models.IPluginsProvider)GetValue(PluginProviderProperty);
}
set
{
SetValue(PluginProviderProperty, value);
vm.SetPluginSource(PluginProvider);
}
}...
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
}
private Models.IPluginsProvider _actionProvider;
public Models.IPluginsProvider ActionProvider
{
get { return _actionProvider; }
set
{
_actionProvider = value;
OnPropertyChanged("ActionProvider");
}
}
protected void OnPropertyChanged(string property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) //HANDLER IS ALWAYS NULL
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
Using my UserControl in MainWindow.xaml
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider}" />
</Grid>
I don't think you can pass a parameter in the ctor in xaml.
If you create control in code behind you can pass the parameter in the ctor(Param param)
Not sure if this fits in the MVVM model but I use it a lot in regular code behind
Use a frame in the XAML for a place to put the UserControl
Seems like you are missing the binding source
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" />
</Grid>
since your property ActionProvider is declared in MainWindow so during binding you are required to refer the same source unless you've set it as data context of the window
alternative to above you can also do the below if there is no other data context used in the MainWindow then you can use the original binding you have PluginProvider="{Binding ActionProvider}"
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
DataContext = this;
}
I've set the DataContext to this which will effectively resolve the value of ActionProvider in binding from the instance this
Extra
you may also choose to remove INotifyPropertyChanged from MainWindow as it is already DependencyObject and capable of property notification and declare a DependencyProperty for ActionProvider
eg
public Models.IPluginsProvider ActionProvider
{
get { return (Models.IPluginsProvider)GetValue(ActionProviderProperty); }
set { SetValue(ActionProviderProperty, value); }
}
// Using a DependencyProperty as the backing store for ActionProvider. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActionProviderProperty =
DependencyProperty.Register("ActionProvider", typeof(Models.IPluginsProvider), typeof(MainWindow), new PropertyMetadata(null));
so you don't need to worry for the notification change manually, you might be required to use this if the above solution does not work for you otherwise it is good to have.