Why ActualWidth is always 0 after DrawingContext drew? - wpf

Recently I have been trying to rewrite TextBlock for letterspacing. Now I almost achieved this by the DrawingContext.
Here is my code:
public class STextBlock : Control
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(STextBlock), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender));
public double LetterSpacing
{
get { return (double)GetValue(LetterSpacingProperty); }
set { SetValue(LetterSpacingProperty, value); }
}
// Using a DependencyProperty as the backing store for LetterSpacing. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LetterSpacingProperty =
DependencyProperty.Register("LetterSpacing", typeof(double), typeof(STextBlock), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
public FontStyle FontStyle
{
get { return (FontStyle)GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
// Using a DependencyProperty as the backing store for FontStyle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontStyleProperty =
DependencyProperty.Register("FontStyle", typeof(FontStyle), typeof(STextBlock), new FrameworkPropertyMetadata(FontStyles.Normal, FrameworkPropertyMetadataOptions.AffectsRender));
public Brush Foreground
{
get { return (Brush)GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
// Using a DependencyProperty as the backing store for Foreground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ForegroundProperty =
DependencyProperty.Register("Foreground", typeof(Brush), typeof(STextBlock), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Black), FrameworkPropertyMetadataOptions.AffectsRender));
public FontStretch FontStretch
{
get { return (FontStretch)GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
// Using a DependencyProperty as the backing store for FontStretch. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontStretchProperty =
DependencyProperty.Register("FontStretch", typeof(FontStretch), typeof(STextBlock), new FrameworkPropertyMetadata(FontStretches.Normal, FrameworkPropertyMetadataOptions.AffectsRender));
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
// Using a DependencyProperty as the backing store for FontFamily. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily", typeof(FontFamily), typeof(STextBlock), new FrameworkPropertyMetadata(new FontFamily(), FrameworkPropertyMetadataOptions.AffectsRender));
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
// Using a DependencyProperty as the backing store for FontSize. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontSizeProperty =
DependencyProperty.Register("FontSize", typeof(double), typeof(STextBlock), new FrameworkPropertyMetadata(12.0, FrameworkPropertyMetadataOptions.AffectsRender));
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
// Using a DependencyProperty as the backing store for FontWeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontWeightProperty =
DependencyProperty.Register("FontWeight", typeof(FontWeight), typeof(STextBlock), new FrameworkPropertyMetadata(FontWeights.Normal, FrameworkPropertyMetadataOptions.AffectsRender));
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
List<FormattedTextToDrawModel> FormattedTextToDrawList = new List<FormattedTextToDrawModel>();
double PositionX = Padding.Left;
double PositionY = Padding.Top;
if (!string.IsNullOrEmpty(Text))
{
foreach (var i in Text)
{
var FT = new FormattedText(i.ToString(), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), FontSize, Foreground);
FormattedTextToDrawList.Add(new FormattedTextToDrawModel() { FormattedText = FT, DrawPoint = new Point(PositionX, PositionY) });
PositionX += FT.Width + LetterSpacing;
if (PositionX > ActualSize.Width)
{
ActualSize.Width = PositionX;
}
if (PositionY + FT.Height > ActualSize.Height)
{
ActualSize.Height = PositionY + FT.Height;
}
}
}
ActualSize.Width += Padding.Right;
ActualSize.Height += Padding.Top;
drawingContext.DrawRectangle(Background, new Pen(), new Rect(ActualSize));
foreach (var i in FormattedTextToDrawList)
{
drawingContext.DrawText(i.FormattedText, i.DrawPoint);
}
}
public struct FormattedTextToDrawModel
{
public FormattedText FormattedText { get;init;}
public Point DrawPoint { get; init; }
}
Size ActualSize = new Size(0, 0);
}
I test the code above like this:
<Border VerticalAlignment="Center" HorizontalAlignment="Center">
<Test:STextBlock Margin="10" x:Name="TB" Text="123123" Padding="20" LetterSpacing="4" FontFamily="Comic Sans MS" FontSize="20" Background="Red"></Seal:STextBlock>
</Border>
Now I have a problem. After the DrawingContext drew the background and text. The ActualWidth and ActualHeight not only of the STextBlock self but also of its parent is still being 0. What's wrong with it?
Is there any way I can set the ActualWidth and ActualHeight by myself?

Actual width and height are calculated internally as part of the measure arrange layout pass.
Each control being laid out is asked for it's desired size.
How much space would it like?
That's then compared to what it can have from parent containers etc and it may not get what it desires.
As an example.
A textblock which cannot have the width it would like will have it's text truncated or it will overflow onto the next line.
FrameworkElement.ActualWidth is read only
Note only {get;}
https://learn.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.actualwidth?view=windowsdesktop-7.0
You don't set that directly yourself.
You should instead override measure. I think you've sort of done those calculations.
protected override Size MeasureOverride(Size constraint)
{
Size desiredSize = new Size();
// Calculate how big your text wants to be
return desiredSize;
}

Related

Binding ElementName return an instance from a previously instancied DataTemplate

I have the following DataTemplate
<DataTemplate x:Key="ArchiveModeContentTemplate">
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}"
Command="{Binding ElementName=factory,Path=BuildPopup}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="factory" Factory="{Binding ConfirmArchivingFactory}" />
</i:Interaction.Behaviors>
</Button>
</DataTemplate>
PopupFactory has a Command BuildPopup. this Command is given to the button with a binding with ElementName.
The first time this dataTemplate is displayed, it work fine. The button get the command. But if this dataTemplate is unloaded then displayed again, the binding give to the button the command of the previous instance of PopupFactory and not the newly created instance.
I pass in the constructor of PopupFactory and it is attached to the new button. So it is not a problem of PopupFactory being shared between templates.
Why this is happening? is it a bug with a the xaml cache?
Edit
I have an even stranger bug now.
I changed the syntax to the following to have the binding elementName after the name declaration in the Xaml. Now the command is working correctly but the the second button which is using a binding RelativeSource to find a command named GoBack don't work anymore. I used snoop to check the binding and it complain that it can't find the command BuildPopup. WPF is getting crazy!
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="Archivefactory" Factory="{Binding ConfirmArchivingFactory}" IsSingleInstance="False" />
</i:Interaction.Behaviors>
<Button.Command>
<Binding ElementName="Archivefactory" Path="BuildPopup" />
</Button.Command>
</Button>
<Button Grid.Row="1" Grid.Column="1"
Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}"
Content="{StaticResource CrossIcon48}"
Foreground="Green"
ui:StyleProperties.Label="{DynamicResource Cancel}"
Command="{Binding Path=GoBack, RelativeSource={RelativeSource AncestorType={x:Type ui:DrillDown}}}" />
Edit
Here the code of PopupFactory
public class PopupFactory : Behavior<UIElement>
{
public ICommand BuildPopup { get; private set; }
private bool _canExecute;
private IDisposable _canexecuteSubscription = null;
public IObservable<bool> CanExecuteSource
{
get { return (IObservable<bool>)GetValue(CanExecuteSourceProperty); }
set { SetValue(CanExecuteSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for CanExecute. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CanExecuteSourceProperty =
DependencyProperty.Register("CanExecute", typeof(IObservable<bool>), typeof(PopupFactory), new PropertyMetadata(null));
private static void OnCanExecuteSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
factory._canexecuteSubscription?.Dispose();
if (arg.NewValue != null)
{
factory._canexecuteSubscription = ((IObservable<bool>)arg.NewValue)
.ObserveOnDispatcher()
.Subscribe(factory.UpdateCanExecute);
}
}
private void UpdateCanExecute(bool value)
{
_canExecute = value;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
}
public IFactory Factory
{
get { return (IFactory)GetValue(FactoryProperty); }
set { SetValue(FactoryProperty, value); }
}
// Using a DependencyProperty as the backing store for Factory. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FactoryProperty =
DependencyProperty.Register("Factory", typeof(IFactory), typeof(PopupFactory), new PropertyMetadata(null, OnFactoryChanged));
private static void OnFactoryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
((RelayCommand<object>)factory.BuildPopup).RaiseCanExecuteChanged();
}
public UIElement PlacementTarget
{
get { return (UIElement)GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); }
}
// Using a DependencyProperty as the backing store for PlacementTarget. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementTargetProperty =
DependencyProperty.Register("PlacementTarget", typeof(UIElement), typeof(PopupFactory), new PropertyMetadata(null));
public PlacementMode Placement
{
get { return (PlacementMode)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
// Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(PopupFactory), new PropertyMetadata(PlacementMode.Center));
public bool IsSingleInstance
{
get { return (bool)GetValue(IsSingleInstanceProperty); }
set { SetValue(IsSingleInstanceProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSingleInsance. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSingleInstanceProperty =
DependencyProperty.Register("IsSingleInstance", typeof(bool), typeof(PopupFactory), new PropertyMetadata(false));
private bool _singleInstanceShowed = false;
public PopupFactory()
{
BuildPopup = new RelayCommand<object>((f) =>
{
ShowPopup(f);
}, (p) =>
{
return _canExecute && Factory != null && !_singleInstanceShowed;
});
UpdateCanExecute(true);
}
public IOverlayContainer ShowPopup(object parameter)
{
var param = new PopupParameter() { Owner = AssociatedObject };
UIElement target = PlacementTarget != null ? PlacementTarget : AssociatedObject;
var item = Factory.Build(parameter);
param.Content = item.Item;
param.Owner = AssociatedObject;
param.RemoveCondition = item.DisposeStream;
var container = OverlayManager.ShowPopup(param);
var placement = new PopupRelativePlacement(container as FrameworkElement, target,
Placement, false);
item.PostFactory?.Invoke();
if (IsSingleInstance)
{
_singleInstanceShowed = true;
OverlayManager.PopupOperations.Where((op) => op.Id == container.Id && op.Operationtype == OverlayOperation.OpType.PopupRemoved)
.Once((_) =>
{
_singleInstanceShowed = false;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
});
}
return container;
}
}
Problem solved.
I moved the PopupFactory Behavior to a visual parent of the button. This way, the behavior is created before the button and WPF don't mess up the name resolution during the binding.

How to use calculated and customized DependecyProperty in UserControl?

I want to make an EllipticArc for a project and I created a UserControl to make it. In my code I need to calculate the EndPoint of the Arc according to the Angle , Radius and IsLargeArc. I wrote the code below but it didn't work, so nothing show up in the designer. I can't understand why this is happening. How can I solve it?
EllipticArc.cs
public partial class EllipticArc: UserControl {
public EllipticArc() {
InitializeComponent();
}
public double Radius {
get {
return (int) GetValue(RadiusProperty);
}
set {
SetValue(RadiusProperty, value);
}
}
// Using a DependencyProperty as the backing store for Yaricap. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(EllipticArc), new PropertyMetadata(20 d));
public Point BeginPoint {
get {
return (Point) GetValue(BeginPointProperty);
}
set {
SetValue(BeginPointProperty, value);
}
}
// Using a DependencyProperty as the backing store for BeginPoint. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BeginPointProperty = DependencyProperty.Register("BeginPoint", typeof(Point), typeof(EllipticArc), new PropertyMetadata(new Point(0 d, 0 d)));
public double Angle {
get {
return (int) GetValue(AngleProperty);
}
set {
SetValue(AngleProperty, value);
}
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(EllipticArc), new PropertyMetadata(90 d));
public bool IsLarge {
get {
SetValue(IsLargeProperty, Angle >= 180 ? true: false);
return (bool) GetValue(IsLargeProperty);
}
set {
SetValue(IsLargeProperty, value);
}
}
// Using a DependencyProperty as the backing store for IsLarge. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsLargeProperty = DependencyProperty.Register("IsLarge", typeof(bool), typeof(EllipticArc), new PropertyMetadata(false));
Point CalculateEndPoint(Point beginPoint, double radius, double angle) {
beginPoint.X = beginPoint.X + radius;
beginPoint.Y = beginPoint.Y + radius;
double xplus = Math.Sin(angle) * radius + beginPoint.X;
double yplus = Math.Cos(angle) * radius + beginPoint.Y;
return new Point(xplus, yplus);
}
public Point EndPoint {
get {
SetValue(EndPointProperty, CalculateEndPoint(BeginPoint, Radius, Angle));
return (Point) GetValue(EndPointProperty);
}
set {
SetValue(EndPointProperty, value);
}
}
// Using a DependencyProperty as the backing store for EndPoint. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EndPointProperty = DependencyProperty.Register("EndPoint", typeof(Point), typeof(EllipticArc), new PropertyMetadata(0));
public Size BarSize {
get {
SetValue(BarSizeProperty, new Size(Radius * 2, Radius * 2));
return (Size) GetValue(BarSizeProperty);
}
set {
SetValue(BarSizeProperty, value);
}
}
// Using a DependencyProperty as the backing store for BarSize. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BarSizeProperty = DependencyProperty.Register("BarSize", typeof(Size), typeof(EllipticArc), new PropertyMetadata(new Size(40, 40)));
}
EllipticArc.xaml
<UserControl x:Class="WpfApplication2.EllipticArc"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Path>
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="{Binding BeginPoint}">
<ArcSegment Size="{Binding BarSize}" Point="{Binding EndPoint}"
SweepDirection="Clockwise" IsLargeArc="{Binding IsLarge}"
RotationAngle="0"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</UserControl>

WPF Custom UserControl - Dependency Property Binding

I created a UserControl in WPF and generated some Dependency Properties.
But on one of the Properties i cannot set a Binding in XAML.
internal Visibility ProgressbarVisibility
{
get { return (Visibility)GetValue(ProgressbarVisibilityProperty); }
set { SetValue(ProgressbarVisibilityProperty, value); }
}
internal static readonly DependencyProperty ProgressbarVisibilityProperty =
DependencyProperty.Register("ProgressbarVisibility", typeof(Visibility), typeof(ImportBox), new PropertyMetadata(Visibility.Hidden));
So i get the following Error:
A 'Binding' cannot be set on the 'ProgressbarVisibility' property of
type 'ImportBox'. A 'Binding' can only be set on a DependencyProperty
of a DependencyObject.
When i set the Property "hardcoded" with a fix Value, its no Problem.
The other Dependeny Properties dont throw Errors of this type and i can Bind everthing i want.
internal ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
internal static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(ImportBox));
internal string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
internal static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof(string), typeof(ImportBox));
internal UIElement PresenterContent
{
get { return (UIElement)GetValue(PresenterContentProperty); }
set { SetValue(PresenterContentProperty, value); }
}
internal static readonly DependencyProperty PresenterContentProperty =
DependencyProperty.Register("PresenterContent", typeof(UIElement), typeof(ImportBox));
Make the DependencyProperty as Public will solves your problem...
public Visibility ProgressbarVisibility
{
get { return (Visibility)GetValue(ProgressbarVisibilityProperty); }
set { SetValue(ProgressbarVisibilityProperty, value); }
}
public static readonly DependencyProperty ProgressbarVisibilityProperty =
DependencyProperty.Register("ProgressbarVisibility", typeof(Visibility), typeof(ImportBox), new PropertyMetadata(Visibility.Hidden));

Pushing read-only GUI properties back into ViewModel

I want to write a ViewModel that always knows the current state of some read-only dependency properties from the View.
Specifically, my GUI contains a FlowDocumentPageViewer, which displays one page at a time from a FlowDocument. FlowDocumentPageViewer exposes two read-only dependency properties called CanGoToPreviousPage and CanGoToNextPage. I want my ViewModel to always know the values of these two View properties.
I figured I could do this with a OneWayToSource databinding:
<FlowDocumentPageViewer
CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>
If this was allowed, it would be perfect: whenever the FlowDocumentPageViewer's CanGoToNextPage property changed, the new value would get pushed down into the ViewModel's NextPageAvailable property, which is exactly what I want.
Unfortunately, this doesn't compile: I get an error saying 'CanGoToPreviousPage' property is read-only and cannot be set from markup. Apparently read-only properties don't support any kind of databinding, not even databinding that's read-only with respect to that property.
I could make my ViewModel's properties be DependencyProperties, and make a OneWay binding going the other way, but I'm not crazy about the separation-of-concerns violation (ViewModel would need a reference to the View, which MVVM databinding is supposed to avoid).
FlowDocumentPageViewer doesn't expose a CanGoToNextPageChanged event, and I don't know of any good way to get change notifications from a DependencyProperty, short of creating another DependencyProperty to bind it to, which seems like overkill here.
How can I keep my ViewModel informed of changes to the view's read-only properties?
Yes, I've done this in the past with the ActualWidth and ActualHeight properties, both of which are read-only. I created an attached behavior that has ObservedWidth and ObservedHeight attached properties. It also has an Observe property that is used to do the initial hook-up. Usage looks like this:
<UserControl ...
SizeObserver.Observe="True"
SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"
So the view model has Width and Height properties that are always in sync with the ObservedWidth and ObservedHeight attached properties. The Observe property simply attaches to the SizeChanged event of the FrameworkElement. In the handle, it updates its ObservedWidth and ObservedHeight properties. Ergo, the Width and Height of the view model is always in sync with the ActualWidth and ActualHeight of the UserControl.
Perhaps not the perfect solution (I agree - read-only DPs should support OneWayToSource bindings), but it works and it upholds the MVVM pattern. Obviously, the ObservedWidth and ObservedHeight DPs are not read-only.
UPDATE: here's code that implements the functionality described above:
public static class SizeObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(SizeObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached(
"ObservedWidth",
typeof(double),
typeof(SizeObserver));
public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached(
"ObservedHeight",
typeof(double),
typeof(SizeObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObserveProperty, observe);
}
public static double GetObservedWidth(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(FrameworkElement frameworkElement, double observedWidth)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedWidthProperty, observedWidth);
}
public static double GetObservedHeight(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(FrameworkElement frameworkElement, double observedHeight)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedHeightProperty, observedHeight);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.SizeChanged += OnFrameworkElementSizeChanged;
UpdateObservedSizesForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged;
}
}
private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateObservedSizesForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement)
{
// WPF 4.0 onwards
frameworkElement.SetCurrentValue(ObservedWidthProperty, frameworkElement.ActualWidth);
frameworkElement.SetCurrentValue(ObservedHeightProperty, frameworkElement.ActualHeight);
// WPF 3.5 and prior
////SetObservedWidth(frameworkElement, frameworkElement.ActualWidth);
////SetObservedHeight(frameworkElement, frameworkElement.ActualHeight);
}
}
I use a universal solution which works not only with ActualWidth and ActualHeight, but also with any data you can bind to at least in reading mode.
The markup looks like this, provided ViewportWidth and ViewportHeight are properties of the view model
<Canvas>
<u:DataPiping.DataPipes>
<u:DataPipeCollection>
<u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualWidth}"
Target="{Binding Path=ViewportWidth, Mode=OneWayToSource}"/>
<u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualHeight}"
Target="{Binding Path=ViewportHeight, Mode=OneWayToSource}"/>
</u:DataPipeCollection>
</u:DataPiping.DataPipes>
<Canvas>
Here is the source code for the custom elements
public class DataPiping
{
#region DataPipes (Attached DependencyProperty)
public static readonly DependencyProperty DataPipesProperty =
DependencyProperty.RegisterAttached("DataPipes",
typeof(DataPipeCollection),
typeof(DataPiping),
new UIPropertyMetadata(null));
public static void SetDataPipes(DependencyObject o, DataPipeCollection value)
{
o.SetValue(DataPipesProperty, value);
}
public static DataPipeCollection GetDataPipes(DependencyObject o)
{
return (DataPipeCollection)o.GetValue(DataPipesProperty);
}
#endregion
}
public class DataPipeCollection : FreezableCollection<DataPipe>
{
}
public class DataPipe : Freezable
{
#region Source (DependencyProperty)
public object Source
{
get { return (object)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(object), typeof(DataPipe),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSourceChanged)));
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DataPipe)d).OnSourceChanged(e);
}
protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e)
{
Target = e.NewValue;
}
#endregion
#region Target (DependencyProperty)
public object Target
{
get { return (object)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(object), typeof(DataPipe),
new FrameworkPropertyMetadata(null));
#endregion
protected override Freezable CreateInstanceCore()
{
return new DataPipe();
}
}
If anyone else is interested, I coded up an approximation of Kent's solution here:
class SizeObserver
{
#region " Observe "
public static bool GetObserve(FrameworkElement elem)
{
return (bool)elem.GetValue(ObserveProperty);
}
public static void SetObserve(
FrameworkElement elem, bool value)
{
elem.SetValue(ObserveProperty, value);
}
public static readonly DependencyProperty ObserveProperty =
DependencyProperty.RegisterAttached("Observe", typeof(bool), typeof(SizeObserver),
new UIPropertyMetadata(false, OnObserveChanged));
static void OnObserveChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = depObj as FrameworkElement;
if (elem == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
elem.SizeChanged += OnSizeChanged;
else
elem.SizeChanged -= OnSizeChanged;
}
static void OnSizeChanged(object sender, RoutedEventArgs e)
{
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
FrameworkElement elem = e.OriginalSource as FrameworkElement;
if (elem != null)
{
SetObservedWidth(elem, elem.ActualWidth);
SetObservedHeight(elem, elem.ActualHeight);
}
}
#endregion
#region " ObservedWidth "
public static double GetObservedWidth(DependencyObject obj)
{
return (double)obj.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(DependencyObject obj, double value)
{
obj.SetValue(ObservedWidthProperty, value);
}
// Using a DependencyProperty as the backing store for ObservedWidth. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ObservedWidthProperty =
DependencyProperty.RegisterAttached("ObservedWidth", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));
#endregion
#region " ObservedHeight "
public static double GetObservedHeight(DependencyObject obj)
{
return (double)obj.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(DependencyObject obj, double value)
{
obj.SetValue(ObservedHeightProperty, value);
}
// Using a DependencyProperty as the backing store for ObservedHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ObservedHeightProperty =
DependencyProperty.RegisterAttached("ObservedHeight", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));
#endregion
}
Feel free to use it in your apps. It works well. (Thanks Kent!)
Here is another solution to this "bug" which I blogged about here:
OneWayToSource Binding for ReadOnly Dependency Property
It works by using two Dependency Properties, Listener and Mirror. Listener is bound OneWay to the TargetProperty and in the PropertyChangedCallback it updates the Mirror property which is bound OneWayToSource to whatever was specified in the Binding. I call it PushBinding and it can be set on any read-only Dependency Property like this
<TextBlock Name="myTextBlock"
Background="LightBlue">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
Download Demo Project Here.
It contains source code and short sample usage.
One last note, since .NET 4.0 we are even further away from built-in-support for this, since a OneWayToSource Binding reads the value back from the Source after it has updated it
I like Dmitry Tashkinov's solution!
However it crashed my VS in design mode. That's why I added a line to OnSourceChanged method:
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!((bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
((DataPipe)d).OnSourceChanged(e);
}
I think it can be done a bit simpler:
xaml:
behavior:ReadOnlyPropertyToModelBindingBehavior.ReadOnlyDependencyProperty="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
behavior:ReadOnlyPropertyToModelBindingBehavior.ModelProperty="{Binding MyViewModelProperty}"
cs:
public class ReadOnlyPropertyToModelBindingBehavior
{
public static readonly DependencyProperty ReadOnlyDependencyPropertyProperty = DependencyProperty.RegisterAttached(
"ReadOnlyDependencyProperty",
typeof(object),
typeof(ReadOnlyPropertyToModelBindingBehavior),
new PropertyMetadata(OnReadOnlyDependencyPropertyPropertyChanged));
public static void SetReadOnlyDependencyProperty(DependencyObject element, object value)
{
element.SetValue(ReadOnlyDependencyPropertyProperty, value);
}
public static object GetReadOnlyDependencyProperty(DependencyObject element)
{
return element.GetValue(ReadOnlyDependencyPropertyProperty);
}
private static void OnReadOnlyDependencyPropertyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
SetModelProperty(obj, e.NewValue);
}
public static readonly DependencyProperty ModelPropertyProperty = DependencyProperty.RegisterAttached(
"ModelProperty",
typeof(object),
typeof(ReadOnlyPropertyToModelBindingBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static void SetModelProperty(DependencyObject element, object value)
{
element.SetValue(ModelPropertyProperty, value);
}
public static object GetModelProperty(DependencyObject element)
{
return element.GetValue(ModelPropertyProperty);
}
}

OneWayToSource binding from readonly property in XAML

I'm trying to bind to a Readonly property with OneWayToSource as mode, but it seems this cannot be done in XAML:
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWayToSource}" />
I get:
The property 'FlagThingy.IsModified' cannot be set because it does not have an accessible set accessor.
IsModified is a readonly DependencyProperty on FlagThingy. I want to bind that value to the FlagIsModified property on the container.
To be clear:
FlagThingy.IsModified --> container.FlagIsModified
------ READONLY ----- ----- READWRITE --------
Is this possible using just XAML?
Update: Well, I fixed this case by setting the binding on the container and not on the FlagThingy. But I'd still like to know if this is possible.
Some research results for OneWayToSource...
Option # 1.
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Binding binding = new Binding();
binding.Path = new PropertyPath("FlagIsModified");
binding.ElementName = "container";
binding.Mode = BindingMode.OneWayToSource;
_flagThingy.SetBinding(FlagThingy.IsModifiedProperty, binding);
Option # 2
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
}
<controls:FlagThingy IsModified="{Binding Path=FlagIsModified,
ElementName=container, Mode=OneWayToSource}" />
Option # 3 (True read-only dependency property)
System.ArgumentException: 'IsModified' property cannot be data-bound.
// Control definition
public partial class FlagThingy : UserControl
{
private static readonly DependencyPropertyKey IsModifiedKey =
DependencyProperty.RegisterReadOnly("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public static readonly DependencyProperty IsModifiedProperty =
IsModifiedKey.DependencyProperty;
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Same binding code...
Reflector gives the answer:
internal static BindingExpression CreateBindingExpression(DependencyObject d, DependencyProperty dp, Binding binding, BindingExpressionBase parent)
{
FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata;
if (((fwMetaData != null) && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly)
{
throw new ArgumentException(System.Windows.SR.Get(System.Windows.SRID.PropertyNotBindable, new object[] { dp.Name }), "dp");
}
....
This is a limitation of WPF and it is by design. It is reported on Connect here:
OneWayToSource binding from a readonly dependency property
I made a solution to dynamically be able to push read-only dependency properties to the source called PushBinding which I blogged about here. The example below does OneWayToSource Bindings from the read-only DP's ActualWidth and ActualHeight to the Width and Height properties of the DataContext
<TextBlock Name="myTextBlock">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
PushBinding works by using two Dependency Properties, Listener and Mirror. Listener is bound OneWay to the TargetProperty and in the PropertyChangedCallback it updates the Mirror property which is bound OneWayToSource to whatever was specified in the Binding.
Demo Project can be Downloaded Here.
It contains source code and short sample usage.
Wrote this:
Usage:
<TextBox Text="{Binding Text}"
p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty},
To=SomeDataContextProperty}" />
Code:
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
public static class OneWayToSource
{
public static readonly DependencyProperty BindProperty = DependencyProperty.RegisterAttached(
"Bind",
typeof(ProxyBinding),
typeof(OneWayToSource),
new PropertyMetadata(default(Paths), OnBindChanged));
public static void SetBind(this UIElement element, ProxyBinding value)
{
element.SetValue(BindProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static ProxyBinding GetBind(this UIElement element)
{
return (ProxyBinding)element.GetValue(BindProperty);
}
private static void OnBindChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ProxyBinding)e.OldValue)?.Dispose();
}
public class ProxyBinding : DependencyObject, IDisposable
{
private static readonly DependencyProperty SourceProxyProperty = DependencyProperty.Register(
"SourceProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object), OnSourceProxyChanged));
private static readonly DependencyProperty TargetProxyProperty = DependencyProperty.Register(
"TargetProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object)));
public ProxyBinding(DependencyObject source, DependencyProperty sourceProperty, string targetProperty)
{
var sourceBinding = new Binding
{
Path = new PropertyPath(sourceProperty),
Source = source,
Mode = BindingMode.OneWay,
};
BindingOperations.SetBinding(this, SourceProxyProperty, sourceBinding);
var targetBinding = new Binding()
{
Path = new PropertyPath($"{nameof(FrameworkElement.DataContext)}.{targetProperty}"),
Mode = BindingMode.OneWayToSource,
Source = source
};
BindingOperations.SetBinding(this, TargetProxyProperty, targetBinding);
}
public void Dispose()
{
BindingOperations.ClearAllBindings(this);
}
private static void OnSourceProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(TargetProxyProperty, e.NewValue);
}
}
}
[MarkupExtensionReturnType(typeof(OneWayToSource.ProxyBinding))]
public class Paths : MarkupExtension
{
public DependencyProperty From { get; set; }
public string To { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = (UIElement)provideValueTarget.TargetObject;
return new OneWayToSource.ProxyBinding(targetObject, this.From, this.To);
}
}
Have not tested it in styles and templates yet, guess it needs special casing.
Here is another implementation for binding to Validation.HasError
public static class OneWayToSource
{
public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached(
"Bindings",
typeof(OneWayToSourceBindings),
typeof(OneWayToSource),
new PropertyMetadata(default(OneWayToSourceBindings), OnBinidngsChanged));
public static void SetBindings(this FrameworkElement element, OneWayToSourceBindings value)
{
element.SetValue(BindingsProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static OneWayToSourceBindings GetBindings(this FrameworkElement element)
{
return (OneWayToSourceBindings)element.GetValue(BindingsProperty);
}
private static void OnBinidngsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((OneWayToSourceBindings)e.OldValue)?.ClearValue(OneWayToSourceBindings.ElementProperty);
((OneWayToSourceBindings)e.NewValue)?.SetValue(OneWayToSourceBindings.ElementProperty, d);
}
}
public class OneWayToSourceBindings : FrameworkElement
{
private static readonly PropertyPath DataContextPath = new PropertyPath(nameof(DataContext));
private static readonly PropertyPath HasErrorPath = new PropertyPath($"({typeof(Validation).Name}.{Validation.HasErrorProperty.Name})");
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register(
nameof(HasError),
typeof(bool),
typeof(OneWayToSourceBindings),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
internal static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
"Element",
typeof(UIElement),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(UIElement), OnElementChanged));
private static readonly DependencyProperty HasErrorProxyProperty = DependencyProperty.RegisterAttached(
"HasErrorProxy",
typeof(bool),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(bool), OnHasErrorProxyChanged));
public bool HasError
{
get { return (bool)this.GetValue(HasErrorProperty); }
set { this.SetValue(HasErrorProperty, value); }
}
private static void OnHasErrorProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(HasErrorProperty, e.NewValue);
}
private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
BindingOperations.ClearBinding(d, DataContextProperty);
BindingOperations.ClearBinding(d, HasErrorProxyProperty);
}
else
{
var dataContextBinding = new Binding
{
Path = DataContextPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, DataContextProperty, dataContextBinding);
var hasErrorBinding = new Binding
{
Path = HasErrorPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, HasErrorProxyProperty, hasErrorBinding);
}
}
}
Usage in xaml
<StackPanel>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}">
<local:OneWayToSource.Bindings>
<local:OneWayToSourceBindings HasError="{Binding HasError}" />
</local:OneWayToSource.Bindings>
</TextBox>
<CheckBox IsChecked="{Binding HasError, Mode=OneWay}" />
</StackPanel>
This implementation is specific to binding Validation.HasError
Here's another attached property solution based on SizeObserver detailed here Pushing read-only GUI properties back into ViewModel
public static class MouseObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(MouseObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedMouseOverProperty = DependencyProperty.RegisterAttached(
"ObservedMouseOver",
typeof(bool),
typeof(MouseObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.SetValue(ObserveProperty, observe);
}
public static bool GetObservedMouseOver(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObservedMouseOverProperty);
}
public static void SetObservedMouseOver(FrameworkElement frameworkElement, bool observedMouseOver)
{
frameworkElement.SetValue(ObservedMouseOverProperty, observedMouseOver);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.MouseEnter += OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave += OnFrameworkElementMouseOverChanged;
UpdateObservedMouseOverForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.MouseEnter -= OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave -= OnFrameworkElementMouseOverChanged;
}
}
private static void OnFrameworkElementMouseOverChanged(object sender, MouseEventArgs e)
{
UpdateObservedMouseOverForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedMouseOverForFrameworkElement(FrameworkElement frameworkElement)
{
frameworkElement.SetCurrentValue(ObservedMouseOverProperty, frameworkElement.IsMouseOver);
}
}
Declare attached property in control
<ListView ItemsSource="{Binding SomeGridItems}"
ut:MouseObserver.Observe="True"
ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">
WPF will not use the CLR property setter, but seems it does some odd validation based on it.
May be in your situation this can be ok:
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
Hmmm... I'm not sure I agree with any of these solutions. How about specifying a coercion callback in your property registration that ignores external change? For instance, I needed to implement a read-only Position dependency property to get the position of a MediaElement control inside a user control. Here's how I did it:
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(double), typeof(MediaViewer),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnPositionChanged, OnPositionCoerce));
private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = d as MediaViewer;
}
private static object OnPositionCoerce(DependencyObject d, object value)
{
var ctrl = d as MediaViewer;
var position = ctrl.MediaRenderer.Position.TotalSeconds;
if (ctrl.MediaRenderer.NaturalDuration.HasTimeSpan == false)
return 0d;
else
return Math.Min(position, ctrl.Duration);
}
public double Position
{
get { return (double)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
In other words, simply ignore the change and return the value backed by a different member that does not have a public modifier. -- In the above example, MediaRenderer is actually the private MediaElement control.
The way I worked around this limitation was to expose only a Binding property in my class, keeping the DependencyProperty private altogether. I implemented a "PropertyBindingToSource" write-only property (this one not a DependencyProperty) which can be set to a binding value in the xaml. In the setter for this write-only property I call to BindingOperations.SetBinding to link the binding to the DependencyProperty.
For the OP's specific example, it would look like this:
The FlatThingy implementation:
public partial class FlatThingy : UserControl
{
public FlatThingy()
{
InitializeComponent();
}
public Binding IsModifiedBindingToSource
{
set
{
if (value?.Mode != BindingMode.OneWayToSource)
{
throw new InvalidOperationException("IsModifiedBindingToSource must be set to a OneWayToSource binding");
}
BindingOperations.SetBinding(this, IsModifiedProperty, value);
}
}
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
private set { SetValue(IsModifiedProperty, value); }
}
private static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlatThingy), new PropertyMetadata(false));
private void Button_Click(object sender, RoutedEventArgs e)
{
IsModified = !IsModified;
}
}
Notice that the static readonly DependencyProperty object is private. In the control I added a button whose click is handled by Button_Click.
The use of the FlatThingy control in my window.xaml:
<Window x:Class="ReadOnlyBinding.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:ReadOnlyBinding"
mc:Ignorable="d"
DataContext="{x:Static local:ViewModel.Instance}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding FlagIsModified}" Grid.Row="0" />
<local:FlatThingy IsModifiedBindingToSource="{Binding FlagIsModified, Mode=OneWayToSource}" Grid.Row="1" />
</Grid>
Note that I've also implemented a ViewModel for binding to that is not shown here. It exposes a DependencyProperty named "FlagIsModified" as you can glean from the source above.
It works great, allowing me to push information back into the ViewModel from the View in a loosely coupled manner, with the direction of that information flow explicitly defined.
You're doing the binding in the wrong direction right now. OneWayToSource will try and update FlagIsModified on container whenever IsModified changes on the control you are creating. You want the opposite, which is to have IsModified bind to container.FlagIsModified. For that you should use the binding mode OneWay
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWay}" />
Full list of enumeration members: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx

Resources