How to set a dependency property from InLine event of a textblock? - wpf

I'm trying to set the dependency property "WordPad" from within the Inline event of MouseEnter from a CustomTextBlock. But this error results:
An object reference is required for the non-static field, method, or property 'WpfCustomControlLibrary.CustomTextBlock.WordPad.get'
How can I achieve this?
Any help would be most appreciated. Thanks.
Given the following class:
public class CustomTextBlock : TextBlock
{
public string InLineText
{
get { return (string)GetValue(InLineTextProperty); }
set { SetValue(InLineTextProperty, value); }
}
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) =>
{
//PropertyChangedCallback
CustomTextBlock tb = (CustomTextBlock)o;
string text = (string)e.NewValue;
tb.Inlines.Clear();
if (String.IsNullOrEmpty(text))
return;
List<Inline> inlines = new List<Inline>();
string[] words = Regex.Split(text, #"(\s+)");
Inline inline = null;
foreach (string s in words)
{
Run run = new Run(s);
inline = run;
inline.MouseEnter += new System.Windows.Input.MouseEventHandler(inline_MouseEnter);
inline.MouseLeave += new System.Windows.Input.MouseEventHandler(inline_MouseLeave);
tb.Inlines.Add(inline);
}
}));
public WritingPad WordPad
{
get { return (WritingPad)GetValue(WordPadProperty); }
set { SetValue(WordPadProperty, value); }
}
public static readonly DependencyProperty WordPadProperty =
DependencyProperty.Register("WordPad", typeof(WritingPad), typeof(CustomTextBlock), new UIPropertyMetadata(null));
static void inline_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Run Sender = sender as Run;
TextPointer tp0 = Sender.ContentStart;
TextPointer tp1 = Sender.ContentEnd;
Rect StartRect = tp0.GetCharacterRect(LogicalDirection.Forward);
Rect EndRect = tp1.GetCharacterRect(LogicalDirection.Backward);
StartRect.Union(EndRect);
WordPad = new WritingPad(); <--**THIS FAILS ????
}

Make the inline_MouseEnter and inline_MouseLeave methods non-static and attach them to your Runs like this:
inline.MouseEnter += tb.inline_MouseEnter;
inline.MouseLeave += tb.inline_MouseLeave;
Even better would be to make the whole PropertyChangedCallback non-static and then write the dependency property declaration like this:
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) => ((CustomTextBlock)o).InlineTextChanged((string)e.NewValue)));
private void InlineTextChanged(string text)
{
Inlines.Clear();
if (!string.IsNullOrEmpty(text))
{
foreach (string s in Regex.Split(text, #"(\s+)"))
{
var run = new Run(s);
run.MouseEnter += inline_MouseEnter;
run.MouseLeave += inline_MouseLeave;
Inlines.Add(run);
}
}
}

I'm hopeful a guru can give a better solution.
It should be noted that Run has a "Parent" property which resolved to this instance of CustomTextBlock. Hence, this seems to work,
CustomTextBlock ctb = Sender.Parent as CustomTextBlock;
ctb.WordPad = new WritingPad
{
WordBounds = StartRect,
WritingPadMode = Enumerations.WritingPadModes.EditingByWord
};

Related

Dependency Property design data

I have a UserControl that has an ObservableCollection dependency property with a property changed callback. The callback rebuilds the nodes in a TreeView control. This all works fine but I would like to be able to have design data. Unfortunately, neither the constructor, the callback nor a default value function call is called by the designer unless I embed my control in another. Is there a way loading default data in this scenario?
Below is the code behind for my control
public partial class ScheduleResourcesSummaryTreeView : UserControl
{
public ObservableCollection<PerformanceProjection> SelectedPerformances
{
get { return (ObservableCollection<PerformanceProjection>)GetValue(SelectedPerformancesProperty); }
set { SetValue(SelectedPerformancesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPerformancesProperty =
DependencyProperty.Register("SelectedPerformances",
typeof(ObservableCollection<PerformanceProjection>),
typeof(ScheduleResourcesSummaryTreeView),
new FrameworkPropertyMetadata(
new ObservableCollection<PerformanceProjection>(),
FrameworkPropertyMetadataOptions.AffectsRender, SelectedPerformancesChanged)
);
private static void SelectedPerformancesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ScheduleResourcesSummaryTreeView resourcesTree)) return;
resourcesTree.ResourcesTree.Items.Clear();
var sessions = e.NewValue as ObservableCollection<PerformanceProjection> ?? new ObservableCollection<PerformanceProjection>();
if (sessions.Count == 0) return;
var projections = sessions.SelectMany(x => x.ResourceBookingProjections);
TreeNode<ResourceBookingProjection> resourceBookingTreeRoot = new RvtSummaryTree(new ObservableCollection<ResourceBookingProjection>(projections), "");
foreach (var treeNode in resourceBookingTreeRoot.Children)
{
resourcesTree.ResourcesTree.Items.Add((TreeNode<ResourceBookingProjection>)treeNode);
}
}
private static ObservableCollection<PerformanceProjection> DefaultCollection()
{
var prop = DesignerProperties.IsInDesignModeProperty;
var designMode = (bool) DependencyPropertyDescriptor
.FromProperty(prop, typeof(FrameworkElement))
.Metadata.DefaultValue;
if (!designMode)
return new ObservableCollection<PerformanceProjection>();
var designdata = new PerformanceProjectionsViewModelDesignData();
return designdata.SelectedPerformances;
}
public ScheduleResourcesSummaryTreeView()
{
InitializeComponent();
}
}

In WPF, how do I copy a binding from a TextBlock to custom collection?

Problem: I have a ListBox with TextBlocks whos Text property are bound to different properties. I wish to drag the TextBlock onto a OxyPlot and have the plot create a new LineSeries with a collection that should be bound to the same binding as for the TextBlock (is this making sense?)
I have derived a class from TextBlock to handle the OnMouseMove event like this:
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (CanDrag && (e.LeftButton == MouseButtonState.Pressed))
{
// Make sure we have a data binding
BindingExpression binding = GetBindingExpression(TextProperty);
if(binding == null)
{ return; }
// Package the data.
DataObject data = new DataObject();
data.SetData("DragListText.Binding", binding);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy);
}
}
Also I have derived a class from Oxy.Plot that handles the OnDrop:
protected override void OnDrop(DragEventArgs e)
{
base.OnDrop(e);
// DataObject must contain a DragListText.Binding object
if (e.Data.GetDataPresent("DragListText.Binding"))
{
BindingExpression binding = e.Data.GetData("DragListText.Binding") as BindingExpression;
AddSeries(binding);
}
e.Handled = true;
}
The AddSeries function does the following:
public void AddSeries(BindingExpression binding)
{
plot1 = new PlotCollection();
LineSeries newSeries = new LineSeries();
newSeries.ItemsSource = plot1.Collection;
Series.Add(newSeries);
}
And lastly the PlotCollection is defined as:
public class PlotCollection : DependencyObject
{
public ObservableCollection<DataPoint> Collection;
public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(PlotCollection), new PropertyMetadata(0.0, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ ((PlotCollection)d).AddLast(); }
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public PlotCollection()
{
Collection = new ObservableCollection<DataPoint>();
}
protected void AddLast()
{
Collection.Add(new DataPoint(OxyPlot.Axes.DateTimeAxis.ToDouble(DateTime.Now), Value));
}
}
So my question is this: How do I create a binding on PlotCollection.Value that matches the one from the TextBlock.Text?
In your AddSeries method, try adding this line of code:
BindingOperations.SetBinding(plot1, PlotCollection.ValueProperty, binding.ParentBinding);
Found out the problem,
I needed to add a PropertyChangedCallback to the ValueProperty declaration, like this:
public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DynamicSeries), new PropertyMetadata(0.0, OnValueChanged));
And then handle the property changes in the callback method:
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PlotCollection ds = (PlotCollection)d;
ds.AppendValue((double)e.NewValue);
}
I guess that I have misunderstood how the Value property works?!
Thanks for taking the time to try and help me...

MVVM - How change backcolor for a single char in a datagrid

I would the rows containing the search value (Search Name), show this value (in datagrid) with a different color.
See the pic below.
Some ideas about this ?
You can do this by creating a new control that extends a standard TextBlock, which uses a series of Run items to display the text, using the appropriate formatting.
public class HighlightTextBlock: TextBlock
{
public string BaseText
{
get { return (string)GetValue(BaseTextProperty); }
set { SetValue(BaseTextProperty, value); }
}
public static readonly DependencyProperty BaseTextProperty =
DependencyProperty.Register("BaseText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public string HighlightText
{
get { return (string)GetValue(HighlightTextProperty); }
set { SetValue(HighlightTextProperty, value); }
}
public static readonly DependencyProperty HighlightTextProperty =
DependencyProperty.Register("HighlightText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public Brush HighlightBrush
{
get { return (Brush)GetValue(HighlightBrushProperty); }
set { SetValue(HighlightBrushProperty, value); }
}
public static readonly DependencyProperty HighlightBrushProperty =
DependencyProperty.Register("HighlightBrush", typeof(Brush), typeof(HighlightTextBlock), new PropertyMetadata(Brushes.Orange, UpdateDisplay));
public bool HighlightCaseSensitive
{
get { return (bool)GetValue(HighlightCaseSensitiveProperty); }
set { SetValue(HighlightCaseSensitiveProperty, value); }
}
public static readonly DependencyProperty HighlightCaseSensitiveProperty =
DependencyProperty.Register("HighlightCaseSensitive", typeof(bool), typeof(HighlightTextBlock), new PropertyMetadata(false, UpdateDisplay));
private static void UpdateDisplay(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var hightlightTextBlock = sender as HighlightTextBlock;
if (hightlightTextBlock == null)
return;
hightlightTextBlock.Inlines.Clear();
if (string.IsNullOrEmpty(hightlightTextBlock.BaseText))
return;
if (string.IsNullOrEmpty(hightlightTextBlock.HighlightText))
{
hightlightTextBlock.Inlines.Add(new Run(hightlightTextBlock.BaseText));
return;
}
var textItems = Regex.Split(hightlightTextBlock.BaseText,
"(" + hightlightTextBlock.HighlightText + ")",
hightlightTextBlock.HighlightCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
foreach (var item in textItems)
{
var run = new Run(item);
var highlight = hightlightTextBlock.HighlightCaseSensitive
? string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCulture) == 0
: string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCultureIgnoreCase) == 0;
if (highlight)
run.Background = hightlightTextBlock.HighlightBrush;
hightlightTextBlock.Inlines.Add(run);
}
}
}
The brackets around the HighlightText value tells Regex.Split to include the matched text in the returned list of items.
This control can then be used as part of an item template in your datagrid column definition. See here for an example of how to do that.

WPF - Can List<T> be used as a dependencyProperty

I have a simple search text box. This text box acts as a filter. I've now copied/pasted the code for the 5th time and enough is enough. Time for a custom control.
left and right brackets have been replaced with ()
My custom control will be simple. My problem is I want to have a dependencyProperty on this control that is of type List(T).
I created a test project to proof it out and make sure it works. It works well. Ignore List.
Below is the entire class. The problem is that the only thing holding me up is replacing List (Person) with List(T). Something like List where: T is Object
typeof(List(T) where: T is Object) <= Obviously I can't do that but gives an idea what I'm trying to accomplish.
public class SearchTextBox : TextBox
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("FilterSource", typeof(List<Person>), typeof(SearchTextBox), new UIPropertyMetadata(null)); //I WANT THIS TO BE LIST<T>
public List<Person> FilterSource
{
get
{
return (List<Person>)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public static readonly DependencyProperty FilterPropertyNameProperty =
DependencyProperty.Register("FilterPropertyName", typeof(String), typeof(SearchTextBox), new UIPropertyMetadata());
public String FilterPropertyName
{
get
{
return (String)GetValue(FilterPropertyNameProperty);
}
set
{
SetValue(FilterPropertyNameProperty, value);
}
}
public SearchTextBox()
{
this.KeyUp += new System.Windows.Input.KeyEventHandler(SearchBox_KeyUp);
}
void SearchBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(FilterSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterTheSource);
}
bool FilterTheSource(object obj)
{
if (obj == null) return false;
Type t = obj.GetType();
PropertyInfo pi = t.GetProperty(FilterPropertyName);
//object o = obj.GetType().GetProperty(FilterPropertyName);
String propertyValue = obj.GetType().GetProperty(FilterPropertyName).GetValue(obj, null).ToString().ToLower();
if (propertyValue.Contains(this.Text.ToLower()))
{
return true;
}
return false;
}
}
No; that's not possible.
Instead, just use the non-generic typeof(IList).

GridLength animation using keyframes?

I want to know are there any classes that I can animate a GridLength value using KeyFrames? I have seen the following sites, but none of them were with KeyFrames:
http://windowsclient.net/learn/video.aspx?v=70654
http://marlongrech.wordpress.com/2007/08/20/gridlength-animation/
Any advice?
Create an attached behavior and animate it instead.
Sure, GridLength clearly is not a numeric type and as such it's not clear how it can be animated. To compnesate that I can create an attached behavior like:
public class AnimatableProperties
{
public static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached("Width",
typeof(double),
typeof(DependencyObject),
new PropertyMetadata(-1, (o, e) =>
{
AnimatableProperties.OnWidthChanged((Grid)o, (double)e.NewValue);
}));
public static void SetWidth(DependencyObject o,
double e)
{
o.SetValue(AnimatableProperties.WidthProperty, e);
}
public static double GetWidth(DependencyObject o)
{
return (double)o.GetValue(AnimatableProperties.WidthProperty);
}
private static void OnWidthChanged(DependencyObject target,
double e)
{
target.SetValue(Grid.WidthProperty, new GridLength(e));
}
}
That will re-inroduce Grid width as numeric property of double type. Having that in place you can freely animate it.
P.S. Obviously it doesn't make much sense to use Grid's Width as it's already double. any other GridLength based properties can be wrpapped with double wrappers as per the sample above and then animated via that wrappers.
It is fairly straight forward but you need to use an adapter because you can't directly animate Width on the ColumnDefinition class with a DoubleAnimator because ColumnDefinition is not a double. Here's my code:
public class ColumnDefinitionDoubleAnimationAdapter : Control
{
#region Dependency Properties
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(nameof(Width), typeof(double), typeof(ColumnDefinitionDoubleAnimationAdapter), new PropertyMetadata((double)0, WidthChanged));
private static void WidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var columnDefinitionDoubleAnimationAdapter = (ColumnDefinitionDoubleAnimationAdapter)d;
columnDefinitionDoubleAnimationAdapter.Width = (double)e.NewValue;
}
#endregion
#region Fields
private ColumnDefinition _ColumnDefinition;
#endregion
#region Constructor
public ColumnDefinitionDoubleAnimationAdapter(ColumnDefinition columnDefinition)
{
_ColumnDefinition = columnDefinition;
}
#endregion
#region Public Properties
public double Width
{
get
{
return (double)GetValue(WidthProperty);
}
set
{
SetValue(WidthProperty, value);
_ColumnDefinition.Width = new GridLength(value);
}
}
#endregion
}
Unfortunately the above is pretty inefficient because it creates a GridLength again and again because ColumnDefinition.Width.Value should be read only.
Here is a method to do the animation. It's important that it uses Task based async because otherwise the storyboard will go out of scope and cause bad behaviour. This is good practice anyway so you can await the animation if you need to:
public async static Task AnimateColumnWidth(ColumnDefinition columnDefinition, double from, double to, TimeSpan duration, IEasingFunction ease)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
var storyboard = new Storyboard();
var animation = new DoubleAnimation();
animation.EasingFunction = ease;
animation.Duration = new Duration(duration);
storyboard.Children.Add(animation);
animation.From = from;
animation.To = to;
var columnDefinitionDoubleAnimationAdapter = new ColumnDefinitionDoubleAnimationAdapter(columnDefinition);
Storyboard.SetTarget(animation, columnDefinitionDoubleAnimationAdapter);
Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinitionDoubleAnimationAdapter.WidthProperty));
storyboard.Completed += (a, b) =>
{
taskCompletionSource.SetResult(true);
};
storyboard.Begin();
await taskCompletionSource.Task;
}
And an example usage:
private async void TheMenu_HamburgerToggled(object sender, EventArgs e)
{
TheMenu.IsOpen = !TheMenu.IsOpen;
var twoSeconds = TimeSpan.FromMilliseconds(120);
var ease = new CircleEase { EasingMode = TheMenu.IsOpen ? EasingMode.EaseIn : EasingMode.EaseOut };
if (TheMenu.IsOpen)
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 40, 320, twoSeconds, ease);
}
else
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 320, 40, twoSeconds, ease);
}
}

Resources