I know there are a thousand questions on the topic, but I cannot find what's wrong with mine...
I have a .xaml.cs file with the standard notify property changed stuff, a public member with the "notify" in the setter. I also set the data context in the constructor. Looks like this:
public partial class SlimERDplot : UserControl
{
public SlimERDplot()
{
DataContext = this;
InitializeComponent();
}
public enum AvailableImaginations { NotSet, Right, Left}
private AvailableImaginations _movementImagination = AvailableImaginations.NotSet;
//This is the one I'll be binding
public string HemisphereUI { get { return Hemisphere.ToString() + " hemisphere"; } }
public AvailableHemispheres Hemisphere
{
get { return _hemisphere; }
set
{
if (_hemisphere != value)
{
_hemisphere = value;
NotifyPropertyChanged();
NotifyPropertyChanged("HemisphereUI"); //tried with or without this,
//makes no difference
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The XAML simply:
<Label HorizontalAlignment="Right" Content="{Binding Path=HemisphereUI}"></Label
Then from the consumer XAML I use it like this:
<UIControls:SlimERDplot x:Name="ERDRightDown" Hemisphere="Right" MovementImagination="Left" />
But in the label, it always says NotSet (the default value). I debugged, and the thing does get through the setter with the proper value, but the displayed value is never changed
What's missing?
What's missing?
Your UserControl doesn't implement INotifyPropertyChanged:
public partial class SlimERDplot : UserControl, INotifyPropertyChanged
{
...
}
This is required for the view to actually subscribe to the PropertyChanged event.
You have to use DependencyProperty
Here an example
CodeBehind
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public string HemisphereUI
{
get { return (string)GetValue( HemisphereUIProperty ); }
private set { SetValue( HemisphereUIProperty, value ); }
}
// Using a DependencyProperty as the backing store for HemisphereUI. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HemisphereUIProperty =
DependencyProperty.Register( nameof( HemisphereUI ), typeof( string ), typeof( MyUserControl ), new PropertyMetadata( null ) );
public AvailableHemispheres Hemisphere
{
get { return (AvailableHemispheres)GetValue( HemisphereProperty ); }
set { SetValue( HemisphereProperty, value ); }
}
// Using a DependencyProperty as the backing store for Hemisphere. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HemisphereProperty =
DependencyProperty.Register( nameof( Hemisphere ), typeof( AvailableHemispheres ), typeof( MyUserControl ), new PropertyMetadata( AvailableHemispheres.NotSet, OnHemisphereChanged ) );
private static void OnHemisphereChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
MyUserControl control = ( d as MyUserControl )!;
control.HemisphereUI = e.NewValue.ToString() + " hemisphere";
}
}
public enum AvailableHemispheres
{
NotSet,
Right,
Left,
}
XAML
<UserControl
x:Class="WpfApp1.Views.MyUserControl"
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:local="clr-namespace:WpfApp1.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Label
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{Binding HemisphereUI, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyUserControl}}}" />
</Grid>
</UserControl>
Related
I'm having a problem with a DependencyProperty on a re-usable control I'm creating that plots a single line series using LiveCharts. The issue is that I have 3 dependency properties I want to configure; one is the Values for the chart, one is fill color of the series, and the last is the stroke color of the line series. Here is my XAML:
<UserControl x:Class="DataAnalyzer.Controls.QuickPlotSingleLogFile2"
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:local="clr-namespace:DataAnalyzer.Controls"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="parentControl">
<Grid x:Name="Grid_Container">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<lvc:CartesianChart Name="ChartFile"
Grid.Row="0"
LegendLocation="None"
DisableAnimations="true"
Hoverable="true"
DataTooltip="{x:Null}"
Margin="10"
BorderBrush="Black">
<lvc:CartesianChart.Series>
<lvc:LineSeries x:Name="LineSeries1"
PointGeometry="{x:Null}"
Values="{Binding PlotValues}"
Fill="{Binding FillBrush}"
Stroke="{Binding StrokeBrush}"
AreaLimit="0"></lvc:LineSeries>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.AxisX>
<lvc:Axis Labels=" " Title="Time">
<lvc:Axis.Separator>
<lvc:Separator IsEnabled="False"></lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
</Grid>
And here is the code behind:
public partial class QuickPlotSingleLogFile2 : UserControl, INotifyPropertyChanged
{
// Formatter for the datetime in the x-axis for any series
public Func<double, string> DateTimeSeriesFormatter { get; set; }
#region PlotValues DP
public ChartValues<double> PlotValues {
get { return (ChartValues<double>)GetValue(PlotValuesProperty); }
set { SetValue(PlotValuesProperty, value); }
}
public static readonly DependencyProperty PlotValuesProperty = DependencyProperty.Register("PlotValues", typeof(ChartValues<double>), typeof(QuickPlotSingleLogFile2));
#endregion
#region FillBrush DP
public Brush FillBrush
{
get { return (Brush)GetValue(FillBrushProperty); }
set { SetValue(FillBrushProperty, value); }
}
public static readonly DependencyProperty FillBrushProperty = DependencyProperty.Register("FillBrush", typeof(Brush), typeof(QuickPlotSingleLogFile2), new PropertyMetadata());
#endregion
#region StrokeBrush DP
public Brush StrokeBrush
{
get { return (Brush)GetValue(StrokeBrushProperty); }
set { SetValue(StrokeBrushProperty, value); }
}
public static readonly DependencyProperty StrokeBrushProperty = DependencyProperty.Register("StrokeBrush", typeof(Brush), typeof(QuickPlotSingleLogFile2), new PropertyMetadata(null));
#endregion
public QuickPlotSingleLogFile2()
{
InitializeComponent();
Grid_Container.DataContext = this;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
My question is if there is something more I need to do for a property like "Fill"? The PlotValuesProperty I have configured works exactly as expected -- the binding has no issues. But I cannot get the binding to work for the fill or stroke brush - it somehow gets lost and livecharts provides default values for fill and stroke. This user control is used in a parent window and the data context ends up being the window, which is what I want. I've checked the debugger to ensure that the Data Context is set appropriately, and it seems to work since the Values for the chart are set correctly. But something odd is happening with the fill/stroke.
I figured out the answer to this issue. I don't understand exactly why, but the reason I wasn't seeing the binding working is because I did not initialize the fill/stroke properties in my main window.
For reference, my original code for the main window (truncated to only show the relevant binding for this custom control) was:
public partial class MainWindow
{
#region Binding QuickPlotValues
private ChartValues<double> _quickPlotSingleLogFileValues;
public ChartValues<double> QuickPlotSingleLogFileValues
{
get
{
return _quickPlotSingleLogFileValues;
}
set
{
_quickPlotSingleLogFileValues = value;
OnPropertyChanged("QuickPlotSingleLogFileValues");
}
}
#endregion
#region Binding QuickPlotFill
private Brush _quickPlotFill;
public Brush QuickPlotFill
{
get
{
return _quickPlotFill;
}
set
{
_quickPlotFill = value;
OnPropertyChanged("QuickPlotFill");
}
}
#endregion
#region Binding QuickPlotStroke
private Brush _quickPlotStroke;
public Brush QuickPlotStroke
{
get
{
return _quickPlotStroke;
}
set
{
_quickPlotStroke = value;
OnPropertyChanged("QuickPlotStroke");
}
}
#endregion
}
And the XAML for the custom control is:
<vm:QuickPlotSingleLogFile2 x:Name="PreviewPlotSingleLogFile2"
Grid.Row="1"
Margin="20"
VerticalAlignment="Stretch"
MinHeight="250"
PlotValues="{Binding QuickPlotSingleLogFileValues}"
FillBrush="{Binding QuickPlotFill}"
StrokeBrush="{Binding QuickPlotStroke}"/>
I updated my main window code to the following (initializing the fill/stroke colors):
#region Binding QuickPlotFill
private Brush _quickPlotFill = new SolidColorBrush(Colors.Red);
public Brush QuickPlotFill
{
get
{
return _quickPlotFill;
}
set
{
_quickPlotFill = value;
OnPropertyChanged("QuickPlotFill");
}
}
#endregion
#region Binding QuickPlotStroke
private Brush _quickPlotStroke = new SolidColorBrush(Colors.Green);
public Brush QuickPlotStroke
{
get
{
return _quickPlotStroke;
}
set
{
_quickPlotStroke = value;
OnPropertyChanged("QuickPlotStroke");
}
}
#endregion
And suddenly it worked.
I have browsed the InterWebs enough! I place my hope of resolving this issue here.
I have two parent UserControls, ParentUc1 and ParentUc2. They both include a ChildUc.
Without adding any code except for XAML code, I would like to set the values of the SensorRotationAngle binding in the ChildUc from each of the Parents.
ParentUc1:
Set SensorRotationAngle to 10
ParentUc2:
Set SensorRotationAngle to 20
ChildUc:
<Rectangle>
<Rectangle.RenderTransform>
<RotateTransform Angle="{Binding SensorRotationAngle}" />
</Rectangle.RenderTransform>
</Rectangle>
Thanks!
Since your child user control gets the value from a binding to the SensorRotationAngle property you need to ensure that the DataContext class which is set on your ChildUc has such a property.
So, you could create your child control like this, directly instanciate the view model and set the value of SensorRotationAngle in the process:
<ChildUc>
<ChildUc.DataContext>
<ChildUcViewModel SensorRotationAngle="30"></ChildUcViewModel>
</ChildUc.DataContext>
</ChildUc>
The view model itself could like this:
public class ChildUcViewModel : INotifyPropertyChanged
{
public int SensorRotationAngle
{
get
{
return _sensorRotationAngle;
}
set
{
if (_sensorRotationAngle != value)
{
_sensorRotationAngle = value;
OnPropertyChanged();
}
}
}
int _sensorRotationAngle;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I just tested this on my system, it works.
I believe this is a case of using Value inheritance power from DependencyProperty.
Basically, the childcontrol will inherit the value from the parent control SensorRotationAngle value directly.
public class ParentControlGrid : Grid
{
// Dependency Property
public static readonly DependencyProperty SensorRotationAngleProperty =
DependencyProperty.Register("SensorRotationAngle", typeof(int),
typeof(ParentControlGrid), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
// .NET Property wrapper
public int SensorRotationAngle
{
get { return (int)GetValue(SensorRotationAngleProperty); }
set { SetValue(SensorRotationAngleProperty, value); }
}
}
public class ChildControlTextBox : TextBox
{
// Dependency Property
public static readonly DependencyProperty SensorRotationAngleProperty;
static ChildControlTextBox()
{
SensorRotationAngleProperty = ParentControlGrid.SensorRotationAngleProperty.AddOwner(typeof(ChildControlTextBox),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
}
// .NET Property wrapper
public int SensorRotationAngle
{
get { return (int)GetValue(SensorRotationAngleProperty); }
set { SetValue(SensorRotationAngleProperty, value); }
}
}
<Window x:Class="WpfTestProj.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpfTestProj="clr-namespace:WpfTestProj"
Title="MainWindow" Height="350" Width="525">
<wpfTestProj:ParentControlGrid SensorRotationAngle="500">
<wpfTestProj:ChildControlTextBox Text="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=SensorRotationAngle}" />
</wpfTestProj:ParentControlGrid>
Thank you so much for your help.
I'm trying to understand the ViewToViewModel attribute by getting a small example to work. I've go a couple of questions. My code is below.
Is the [ViewToViewModel] attribute supposed to be placed to be placed in the View, ViewModel or both?
If I try to use an attribute, MappingType, such as: [ViewToViewModel, MappingType = ...] MappingType gives me an error. Am I missing a "using" statement/Assembly Reference? Is there an example of syntax?
I'm able to get things to work the way I need, but I don't think that I'm getting the "ViewToViewModel" part to work properly. In the codebehind of the usercontrol, property changes are handled in HandleMyName(object e). Is ViewToViewModel supposed to do this?
Views:
MainWindow
UserControlView
ViewModels:
MainwindowViewModel
UserControlViewViewmodel
MainWindow
<catel:DataWindow x:Class="ViewToViewModelStudy.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:uc="clr-namespace:ViewToViewModelStudy.Views" >
<StackPanel x:Name="LayoutRoot">
<Label Content="{Binding Title}" />
<uc:UserControlView MyName="{Binding Title}" />
</StackPanel>
</catel:DataWindow>
.
UserControlView.xaml
<catel:UserControl x:Class="ViewToViewModelStudy.Views.UserControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com">
<StackPanel>
<TextBlock>Innerview Model</TextBlock>
<TextBlock Text="{Binding MyName}"></TextBlock>
<TextBlock>Innerview Model</TextBlock>
</StackPanel>
</catel:UserControl>
UserControlView.xaml.cs
namespace ViewToViewModelStudy.Views
{
using Catel.Windows.Controls;
using Catel.MVVM.Views;
using System.Windows;
using System.Data;
public partial class UserControlView : UserControl
{
[ViewToViewModel]
public string MyName
{
get { return (string)GetValue(MyNameProperty); }
set { SetValue(MyNameProperty, value); }
}
public static readonly DependencyProperty MyNameProperty =
DependencyProperty.Register(
"MyName",
typeof(string),
typeof(UserControlView),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnMyName)));
static void OnMyName(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UserControlView ic = (UserControlView)obj;
ic.HandleMyName(e.NewValue);
}
private void HandleMyName(object e)
{
ViewModels.UserControlViewModel vm = (ViewModels.UserControlViewModel)this.ViewModel;
if (vm != null)
{
vm.MyName = e.ToString(); // << Shouldn't this happen automagically?
}
}
public UserControlView()
{
InitializeComponent();
}
}
}
UserControlViewModel.cs
namespace ViewToViewModelStudy.ViewModels
{
using Catel.MVVM;
using Catel.Data;
using Catel.MVVM.Views;
using Catel.Windows.Controls;
public class UserControlViewModel : ViewModelBase
{
public UserControlViewModel()
{ }
public string MyName
{
get { return GetValue<string>(MyNameProperty); }
set { SetValue(MyNameProperty, value); }
}
public static readonly PropertyData MyNameProperty = RegisterProperty("MyName", typeof(string), null, (sender, e) => ((UserControlViewModel)sender).OnMyPropertyChanged());
private void OnMyPropertyChanged()
{
}
}
}
1) A ViewToViewModel should be located in the view (you don't want to pollute your VM with it).
2) The attribute should be used as [ViewToViewModel(MappingType = ...)]
3) The ViewToViewModel should handle the automatic mapping of property x on the view to property x on the view model. It will handle all change notifications automatically.
I have a user control which exposes a property which is a long. I'd like to instantiate this control and bind to the exposed property in a data template.
I'm seeing xaml errors in the resource file. The ambiguous "must have derivative of panel as the root element". And when I run this in a debugger, I see that the value of TeamIdx is -1 and is not being set.
<DataTemplate x:Key="TeamScheduleTemplate">
<Grid HorizontalAlignment="Left" Width="400" Height="600">
<Team:ScheduleControl TeamIdx="{Binding Idx}" />
</Grid>
</DataTemplate>
public sealed partial class ScheduleControl : UserControl
{
public static readonly DependencyProperty TeamIdxProperty =
DependencyProperty.Register(
"TeamIdx",
typeof(long),
typeof(ScheduleControl),
new PropertyMetadata((long)-1));
public long TeamIdx
{
get { return (long)GetValue(TeamIdxProperty); }
set { SetValue(TeamIdxProperty, value); }
}
public ScheduleControl()
{
this.InitializeComponent();
var team = TeamLookup.GetTeam(TeamIdx);
}
}
Edit: It turns out that the binding doesn't happen until after the control is constructed. In retrospect, this makes total sense. The solution I used is below:
public sealed partial class ScheduleControl : UserControl
{
public static readonly DependencyProperty TeamIdxProperty =
DependencyProperty.Register(
"TeamIdx",
typeof(long),
typeof(ScheduleControl),
new PropertyMetadata(
(long)-1,
OnTeamIdxChanged));
public long TeamIdx
{
get { return (long)GetValue(TeamIdxProperty); }
set { SetValue(TeamIdxProperty, value); }
}
private static void OnTeamIdxChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var target = (ScheduleControl)sender;
target.OnTeamIdxChanged((long)e.NewValue);
}
private void OnTeamIdxChanged(long id)
{
var model = FindModel(id);
this.DataContext = model;
}
public ScheduleControl()
{
this.InitializeComponent();
}
}
I'm creating a custom control that has a PasswordBox in it. How do I hook up a DependencyProperty of my custom control to the Password property of the PasswordBox?
From all the examples I see, hooking it up the password in the template using TemplateBinding should do the trick, but this doesn't seem to be working. What am I missing?
generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControlBinding="clr-namespace:CustomControlBinding">
<Style TargetType="CustomControlBinding:PasswordBoxTest">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CustomControlBinding:PasswordBoxTest">
<Grid Background="Transparent">
<PasswordBox Password="{TemplateBinding Text}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
PasswordBoxTest.cs
namespace CustomControlBinding
{
public class PasswordBoxTest : Control
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof( string ), typeof( PasswordBoxTest ), new PropertyMetadata( OnTextPropertyChanged ) );
public string Text
{
get { return GetValue( TextProperty ) as string; }
set { SetValue( TextProperty, value ); }
}
public PasswordBoxTest()
{
DefaultStyleKey = typeof( PasswordBoxTest );
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
private static void OnTextPropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
{
}
}
}
You have to use RelativeSource bindings, for it to work. Or you have to set the DataContext of your UserControl to this.
TemplateBinding is ok. You need to set somehow the binding source (for example through mentioned DataContext or simply in Xaml using Text attribute), but I cannot judge where's the problem since you omitted this code.
I don't know what's the purpose of this class, but probably adding some features to the standard PasswordBox. I would keep both implementations as similar as possible. What I mean the Text property should be called Password etc.
On more remark: The presented template does not need the Grid. Unless you have extra reason for using it, remove it - it just adds on the layout complexity. Note that the default template of the PasswordBox is already wrapped in an identical Grid...
I haven't been able to get this to work no matter what I do. What I did instead that does work is setting up some fake binding in code.
namespace CustomControlBinding
{
public class PasswordBoxTest : Control
{
private PasswordBox passwordBox;
private string passwordSetBeforeTemplateApplied;
public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register( "Password", typeof( string ), new PropertyMetadata( OnPasswordPropertyChanged ) );
public string Password
{
get { return GetValue( PasswordProperty ) as string; }
set { SetValue( PasswordProperty, value ); }
}
public PasswordBoxTest()
{
DefaultStyleKey = typeof( PasswordBoxTest );
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
passwordBox = (PasswordBox)GetTemplateChild( "PasswordElement" );
passwordBox.PasswordChanged += PasswordBoxPasswordChanged;
if( !string.IsNullOrEmpty( passwordSetBeforeTemplateApplied ) )
{
passwordBox.Password = passwordSetBeforeTemplateApplied;
}
}
public static void OnPasswordPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( (WatermarkPasswordBox)d ).OnPasswordChanged( d, e );
}
private void OnPasswordChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
if( passwordBox == null )
{
passwordSetBeforeTemplateApplied = Password;
return;
}
if( Password != passwordBox.Password )
{
passwordBox.Password = Password;
}
}
private void PasswordBoxPasswordChanged( object sender, RoutedEventArgs e )
{
if( passwordBox.Password != Password )
{
Password = passwordBox.Password;
}
}
}
}