Binding two dependency properties from two different usercontrols - wpf

I have created two separate usercontrols, they are meant to work together.
The first one is a simple usercontrol with a thumb attached to it, the thumb makes the control move around by dragging, this is simple and working.
XAML:
<Canvas>
<Thumb x:Name="Thumb" Width="15" Height="15" DragDelta="Thumb_DragDelta"/>
</Canvas>
Code-Behind: A dependency property called Position, when Setter is called it updates the usercontrol's margin.
public partial class ThumbPoint : UserControl
{
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); this.Margin = new Thickness(value.X, value.Y, 0, 0); }
}
// Using a DependencyProperty as the backing store for Position. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position", typeof(Point), typeof(ThumbPoint), new PropertyMetadata(new Point()));
public ThumbPoint()
{
InitializeComponent();
Position = new Point(0, 0);
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Position = new Point(Position.X + e.HorizontalChange, Position.Y + e.VerticalChange);
}
}
The second UserControl is called StraightLine, its composed of a Line control
XAML:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"/>
</Canvas>
Code-Behind: A dependency property called StartPosition, when Setter is called it updates the Line X1 and Y1 (starting position of the line).
public partial class StraightLine : UserControl
{
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); Line.X1 = value.X; Line.Y1 = value.Y; }
}
// Using a DependencyProperty as the backing store for StartPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point()));
public StraightLine()
{
InitializeComponent();
Line.X1 = 0;
Line.Y1 = 0;
Line.X2 = 300;
Line.Y2 = 200;
}
}
Here I am trying to bind them together on the mainwindow.xaml:
<Canvas>
<local:ThumbPoint x:Name="ThumbPoint"/>
<local:StraightLine StartPosition="{Binding Position, ElementName=ThumbPoint}"/>
</Canvas>
Desired effect: DependencyProperty StartPosition of the StraightLine should be updated.
Whats happening: It's not being updated so only the ThumbPoint is moving.

binding doesn't use common property wrappers for DP (public Point StartPosition), it uses SetValue() directly, so code in setter isn't invoked.
What is needed is propertyChangedCallback:
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); }
}
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point(), OnStartPositionChanged));
private static void OnStartPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StraightLine c = (StraightLine) d;
c.Line.X1 = c.StartPosition.X;
c.Line.Y1 = c.StartPosition.Y;
}
in ThumbPoint public Point Position property has the same issue but it works there because you use setter directly: Position = new Point()
Alternatively bind X1 and Y1 value in xaml:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"
X1="{Binding StartPosition.X, RelativeSource={RelativeSource AncestorType=StraightLine}}"
Y1="{Binding StartPosition.Y, RelativeSource={RelativeSource AncestorType=StraightLine}}"/>
</Canvas>
(or use ElementName instead of RelativeSource)

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 enable an editing adorner when the adorned element is disabled in TreeView?

WPF
I have a TreeView (see below) for which I have created an Editing Adorner.
All works correctly. However, I would like to disable the TreeView while editing is in progress. When the TreeView is disabled -- as well as the element being adorned for editing -- the adorner is also being disabled.
How can the TreeView be disabled (IsEnabled = false) but still allow the Adorner to be enabled for editing?
TIA
namespace Doctor_Desk.Views.Adorners
{
public class EditSelectedItemAdorner : Adorner
{
private VisualCollection _Visuals;
private UIElement _UIElement;
private bool _IsCancel = false;
private TextBox _Textbox;
protected override int VisualChildrenCount
{
get
{
return _Visuals.Count;
}
}
// Be sure to call the base class constructor.
public EditSelectedItemAdorner(UIElement adornedElement, IConsultantTreeItem selectedItem)
: base(adornedElement)
{
_UIElement = adornedElement;
adornedElement.Visibility = Visibility.Hidden;
_Textbox = new TextBox
{
Background = Brushes.Pink,
Text = selectedItem.GetText()
};
// The VisualCollection will hold the content of this Adorner.
_Visuals = new VisualCollection(this)
{
// The _Textbox is a logical child of the VisualCollection of the Adorner. The ArrangeOverride and MeasureOverride
// will set up the Grid control for correct rendering.
_Textbox // Adding a single control for display.
};
}
/// The adorner placement is always relative to the top left corner of the adorned element. The children of the adorner are located by
/// implementing ArrangeOverride. The only other way to position the Adorner content is to use the AdornerPanel Class
/// with it's AdornerPlacementCollection Methods.
///
/// Overriding the default ArrangeOverride and MeasureOverride allows a control to be placed and diplayed in the VisualCollection.
protected override Size ArrangeOverride(Size finalSize)
{
//TextBox child = _Visuals[0] as TextBox;
//FrameworkElement element = _UIElement as FrameworkElement;
//if (element != null)
//{
// Size textBoxSize = new Size(Math.Max(150, element.DesiredSize.Width), Math.Max(30, element.DesiredSize.Height));
// Point location = new Point((this.Width - textBoxSize.Width) / 2,
// (textBoxSize.Height - this.Height) / 2);
// child.Arrange(new Rect(location, textBoxSize));
//}
TextBox child = _Visuals[0] as TextBox;
Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
child.Arrange(adornedElementRect);
return base.ArrangeOverride(finalSize);
}
// Overriding the default ArrangeOverride and MeasureOverride allows a control to be diplayed in the VisualCollection.
protected override Size MeasureOverride(Size constraint)
{
_Textbox.Measure(constraint);
return _Textbox.DesiredSize;
}
protected override Visual GetVisualChild(int index)
{
return _Visuals[index];
}
// A common way to implement an adorner's rendering behavior is to override the OnRender
// method, which is called by the layout system as part of a rendering pass.
protected override void OnRender(DrawingContext drawingContext)
{
Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
// Some arbitrary drawing implements.
SolidColorBrush renderBrush = new SolidColorBrush(Colors.Green)
{
Opacity = 0.2
};
Pen renderPen = new Pen(new SolidColorBrush(Colors.Navy), 1.5);
double renderRadius = 5.0;
// Draw a circle at each corner.
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopRight, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomRight, renderRadius, renderRadius);
}
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(EditSelectedItemAdorner), new PropertyMetadata(string.Empty));
}
}
<TreeView Grid.Row="2" Grid.Column="2" Grid.RowSpan="4" Width="400"
ItemsSource="{Binding SpecialityTree}"
>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type tree:SpecialityTreeItem}" ItemsSource="{Binding Offices}" >
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type tree:OfficeTreeItem}" ItemsSource="{Binding Doctors}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type tree:DoctorTreeItem}">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<i:Interaction.Behaviors>
<b:ConsultantTreeViewBehavior
EditSelectedItem="{Binding IsEditing}"
SelectedItem="{Binding SelectedTreeItem, Mode=TwoWay}"
Text="{Binding EditingResult, Mode=OneWayToSource}"
/>
</i:Interaction.Behaviors>
namespace Doctor_Desk.Views.Behaviors
{
public class ConsultantTreeViewBehavior : Behavior<TreeView>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
}
#region SelectedItem Property
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ConsultantTreeViewBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is TreeViewItem item)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
#region [EditSelectedItem]
public bool EditSelectedItem
{
get { return (bool)GetValue(EditSelectedItemProperty); }
set { SetValue(EditSelectedItemProperty, value); }
}
private EditSelectedItemAdorner _Adorner;
public AdornerLayer myAdornerLayer { get; set; }
// Using a DependencyProperty as the backing store for EditSelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EditSelectedItemProperty =
DependencyProperty.Register("EditSelectedItem", typeof(bool), typeof(ConsultantTreeViewBehavior), new PropertyMetadata(false, OnEditSelectedItem));
private static void OnEditSelectedItem(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var d = sender as ConsultantTreeViewBehavior;
var h = d.AssociatedObject as TreeView;
TreeViewItem tvi = h
.ItemContainerGenerator
.ContainerFromItemRecursive(h.SelectedItem);
if (tvi is UIElement myItem)
{
d._Adorner = new EditSelectedItemAdorner(myItem, (IConsultantTreeItem)h.SelectedItem);
// Must have BindingMode.TwoWay for the Adorner to update this ConsultantTreeViewBehavior.
var bindingText = new Binding
{
Source = d,
Path = new PropertyPath(TextProperty),
Mode = BindingMode.TwoWay
};
// The TextProperty binds the Text from the EditSelectedItemAdorner to the ConsultantManagerViewModel.
BindingOperations.SetBinding(d._Adorner, EditSelectedItemAdorner.TextProperty, bindingText);
d.myAdornerLayer = AdornerLayer.GetAdornerLayer(myItem);
d.myAdornerLayer.Add(d._Adorner);
}
}
}
#endregion
#region[Text - xaml binding]
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(ConsultantTreeViewBehavior), new PropertyMetadata(string.Empty,
(s, e) =>
{
}));
#endregion
}
}
Well, in leu of any other answer, it would appear that disabling selection in the TreeView disables all the elements of the TreeView -- including the selected branch. When the selected branch is disabled, so is its editing adorner. Therefore, my workaround is simply to cover that part of the TreeView which is being displayed.
In particular, in the OnRender override, add:
// Find offset of selected item from top of the tree.
GeneralTransform gt = AdornedElement.TransformToVisual(_TreeView);
Point offset_to_tree_top = gt.Inverse.Transform(new Point(0, 0));
drawingContext.DrawRectangle(Brushes.DimGray, null, new Rect(
offset_to_tree_top, _TreeView.DesiredSize));
This puts a DimGray color over the whole tree except for the editing adorner. Thus, user selection of any other tree item can not occur.

Adding a click-event to all textboxes in wpf

in my applications all inputs in textboxes have to be done by a inputbox i wrote myself. so whenever a textbox is klicked, the inputbox pops up. it looks like this:
the textbox in xaml:
<TextBox Text="{Binding Level}" Grid.Row="1" Grid.Column="2" Margin="2">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EnterLevel}" MouseAction="LeftClick" />
</TextBox.InputBindings>
</TextBox>
the command in the VM:
private void ExecuteEnterLevel()
{
var dialog = new BFH.InputBox.InputBox("Level", 1, 1, 4);
dialog.ShowDialog();
Level = Convert.ToInt16(dialog.Input);
}
so the result of the inputbox becomes the text of the inputbox. this works fine.
now my question is: can i do that for all of my textboxes that need that functionality without coding an event for every single textbox? i would like to have "myTextbox" which does it automatically.
what i tried so far:
my textbox:
class MyTextbox : TextBox
{
public MyTextbox()
{
this.MouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox("titel", "input");
dialog.ShowDialog();
tb.Text = dialog.Input;
}
}
and in xaml:
<libraries:MyTextbox/>
but MyTextbox_MouseDown is never executet. i put MessageBox.Show("test") in it without any results. i am doing something wrong i guess.
Since it is not possible to define InputBindings in styles, at least in a straight forward way. I offer to solve the issue by using AttachedProperties
lets start by defining a class for the attached property
namespace CSharpWPF
{
class EventHelper
{
public static ICommand GetLeftClick(DependencyObject obj)
{
return (ICommand)obj.GetValue(LeftClickProperty);
}
public static void SetLeftClick(DependencyObject obj, ICommand value)
{
obj.SetValue(LeftClickProperty, value);
}
// Using a DependencyProperty as the backing store for LeftClick. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LeftClickProperty =
DependencyProperty.RegisterAttached("LeftClick", typeof(ICommand), typeof(EventHelper), new PropertyMetadata(null, OnLeftClickChanged));
private static void OnLeftClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = d as FrameworkElement;
ICommand command = e.NewValue as ICommand;
if (command != null)
elem.InputBindings.Add(new MouseBinding(command, new MouseGesture(MouseAction.LeftClick)));
}
}
}
this is basically translation of mouse binding in your code done on property changed.
usage
<TextBox l:EventHelper.LeftClick="{Binding MyCommand}" />
if you wish to apply to all TextBoxes then wrap the same in a generic style targeted to TextBox
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
l: in the above refers to the name space of the above declared class eg xmlns:l="clr-namespace:CSharpWPF"
a full example
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
</StackPanel.Resources>
<TextBox />
<TextBox />
<TextBox />
<TextBox />
</StackPanel>
Attaching input box behavior
class
class InputHelper
{
public static bool GetIsInputBoxEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsInputBoxEnabledProperty);
}
public static void SetIsInputBoxEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsInputBoxEnabledProperty, value);
}
// Using a DependencyProperty as the backing store for IsInputBoxEnabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsInputBoxEnabledProperty =
DependencyProperty.RegisterAttached("IsInputBoxEnabled", typeof(bool), typeof(InputHelper), new PropertyMetadata(false, OnIsInputBoxEnabled));
private static void OnIsInputBoxEnabled(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
if ((bool)e.NewValue)
tb.PreviewMouseDown += elem_MouseDown;
else
tb.PreviewMouseDown -= elem_MouseDown;
}
static void elem_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBox tb = sender as TextBox;
var dialog = new BFH.InputBox.InputBox(tb.GetValue(InputBoxTitleProperty), tb.GetValue(InputProperty));
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public static string GetInputBoxTitle(DependencyObject obj)
{
return (string)obj.GetValue(InputBoxTitleProperty);
}
public static void SetInputBoxTitle(DependencyObject obj, string value)
{
obj.SetValue(InputBoxTitleProperty, value);
}
// Using a DependencyProperty as the backing store for InputBoxTitle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputBoxTitleProperty =
DependencyProperty.RegisterAttached("InputBoxTitle", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
public static string GetInput(DependencyObject obj)
{
return (string)obj.GetValue(InputProperty);
}
public static void SetInput(DependencyObject obj, string value)
{
obj.SetValue(InputProperty, value);
}
// Using a DependencyProperty as the backing store for Input. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputProperty =
DependencyProperty.RegisterAttached("Input", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
}
usage
<TextBox l:InputHelper.IsInputBoxEnabled="true"
l:InputHelper.InputBoxTitle="Title"
l:InputHelper.Input="Input" />
or via styles
<Style TargetType="TextBox">
<Setter Property="l:InputHelper.IsInputBoxEnabled"
Value="true" />
<Setter Property="l:InputHelper.Title"
Value="true" />
<Setter Property="l:InputHelper.Input"
Value="Input" />
</Style>
using attached properties will enable you to attach this behavior to existing classes instead of inheriting from them.
if you are inheriting the text box class you can add two properties to the same
eg
class MyTextbox : TextBox
{
public MyTextbox()
{
this.PreviewMouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox(InputBoxTitle, Input);
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public string InputBoxTitle { get; set; }
public string Input { get; set; }
}
usage
<libraries:MyTextbox InputBoxTitle="Title"
Input="Input" />

Binding text to attached property

My question is similar to this: WPF Generate TextBlock Inlines but I don't have enough reputation to comment. Here is the attached property class:
public class Attached
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlock),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
I'm using this attached property class and trying to apply it to a textblock to make the text recognize inline values like bold, underline, etc from a string in my view model class. I have the following XAML in my textblock:
<TextBlock Grid.Row="1" Grid.Column="1" TextWrapping="Wrap" my:Attached.FormattedText="test" />
However I get nothing at all in the textblock when I start the program. I also would like to bind the text to a property on my view model eventually but wanted to get something to show up first...
Sorry this is probably a newbie question but I can't figure out why it's not working. It doesn't give me any error here, just doesn't show up. If I try to bind, it gives me the error:
{"A 'Binding' cannot be set on the 'SetFormattedText' property of type 'TextBlock'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject."}
First, the type of property needs to be a class name, not the type TextBlock:
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlock), <----- Here
Second, the handler is not called, it must be registered here:
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
YOUR_PropertyChanged_HANDLER)
Thirdly, an example to work, you need to specify the input string like this:
<Bold>My little text</Bold>
Working example is below:
XAML
<Window x:Class="InlineTextBlockHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:InlineTextBlockHelp"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Name="TestText"
this:AttachedPropertyTest.FormattedText="TestString"
Width="200"
Height="100"
TextWrapping="Wrap" />
<Button Name="TestButton"
Width="100"
Height="30"
VerticalAlignment="Top"
Content="TestClick"
Click="Button_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string inlineExpression = "<Bold>Once I saw a little bird, go hop, hop, hop.</Bold>";
AttachedPropertyTest.SetFormattedText(TestText, inlineExpression);
}
}
public class AttachedPropertyTest
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(AttachedPropertyTest),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
Initially be plain text, after clicking on the Button will be assigned to inline text.
Example for MVVM version
To use this example in MVVM style, you need to create the appropriate property in the Model/ViewModel and associate it with the attached dependency property like this:
<TextBlock Name="TestText"
PropertiesExtension:TextBlockExt.FormattedText="{Binding Path=InlineText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Width="200"
Height="100"
TextWrapping="Wrap" />
Property in Model/ViewModel must support method NotifyPropertyChanged.
Here is a full sample:
AttachedProperty
public class TextBlockExt
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(TextBlockExt),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
MainViewModel
public class MainViewModel : NotificationObject
{
private string _inlineText = "";
public string InlineText
{
get
{
return _inlineText;
}
set
{
_inlineText = value;
NotifyPropertyChanged("InlineText");
}
}
}
MainWindow.xaml
<Window x:Class="InlineTextBlockHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:InlineTextBlockHelp.ViewModels"
xmlns:PropertiesExtension="clr-namespace:InlineTextBlockHelp.PropertiesExtension"
Title="MainWindow" Height="350" Width="525"
ContentRendered="Window_ContentRendered">
<Window.DataContext>
<ViewModels:MainViewModel />
</Window.DataContext>
<Grid>
<TextBlock Name="TestText"
PropertiesExtension:TextBlockExt.FormattedText="{Binding Path=InlineText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Width="200"
Height="100"
TextWrapping="Wrap" />
</Grid>
</Window>
Code-behind (just for test)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
MainViewModel mainViewModel = this.DataContext as MainViewModel;
mainViewModel.InlineText = "<Bold>Once I saw a little bird, go hop, hop, hop.</Bold>";
}
}
This example is available at this link.

Run Animation when Dependency Property Changes

I have created a UserControl with two dependency properties: Value and Color. The Color of the UserControl is dependent on the Value property. For example if Value=0 Color=Blue, Value=0.5 Color=Red and so on. This I have achieved using a custom converter that is bound to the Fill property, like so:
<Ellipse Name="dotForeground" Stroke="Transparent" StrokeThickness="1" Fill="{Binding ElementName=control1, Converter={StaticResource colorConverter}, Path=Value}"/>
Now what I need is that when the Value property changes from for example 0.0 to 0.5, which consequently also changes the Color property, I would want to create a ColorAnimation such that it fades from the previous color to the new color.
I would appreciate any help on this.
There are a few methods to do this, one would be to bind a brush to the Color property instead of using a converter:
<Ellipse Name="dotForeground" Stroke="Transparent" StrokeThickness="1">
<Ellipse.Background>
<SolidColorBrush Color="{Binding Color, ElementName=control1}" />
</Ellipse.Background>
</Ellipse>
Then start a ColorAnimation in your UserControl when the Value changes.
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(MyUserControl), new UIPropertyMetadata(Colors.Red));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(MyUserControl), new UIPropertyMetadata(0.0,ValueChanged));
private static void ValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControl)sender;
var anim = new ColorAnimation { To = Color.FromRgb((byte)control.Value, 128, 60), FillBehavior = FillBehavior.HoldEnd};
control.BeginAnimation(MyUserControl.ColorProperty, anim);
}

Resources