accessing a visual element in code - wpf

I would like to do the equivalent of the following xaml in code, but I don't know how to grab that text block element:
<local:DayOfTheWeekColumn
...
<local:DayOfTheWeekColumn.Header>
<TextBlock
Text="{Binding ...},
ToolTip="{Binding ...} />
</local:DayOfTheWeekColumn.Header>
</local:DayOfTheWeekColumn>
The DayOfTheWeekColumn is a subclass of a DataGridTextColumn. I can get at the Header easily enough and set it's content, and now I want to set the ToolTip in code, figuring the way to do that is just how I am doing it in the xaml above.
Cheers,
Berryl
EDIT =========
Here is the code, so far, for the DayOfTheWeekColumn. The TextBlock in the xaml is part of the Header's visual tree, and not something I want to keep in the xaml. I do want to access it's toolTip though, in code, so I can set it there.
I am thinking there should be a Children property on the column Header that I can access to find the TextBlock, but haven't found that yet.
public class DayOfTheWeekColumn : DataGridTextColumn
{
public static readonly DependencyProperty DowDateProperty = DependencyProperty.RegisterAttached(
"DowDate", typeof (DateTime), typeof (DayOfTheWeekColumn), new PropertyMetadata(OnDateChanged));
public DateTime DowDate
{
get { return (DateTime)GetValue(DowDateProperty); }
set { SetValue(DowDateProperty, value); }
}
private static void OnDateChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {
var col = (DataGridTextColumn) target;
var date = (DateTime) e.NewValue;
col.Header = date.ToString(Strings.ShortDayOfWeekFormat);
//col.Header.ToolTip = "If Only It Were so Easy!!" <==============
}
public DayOfTheWeekColumn() {
Width = 60;
CanUserReorder = false;
CanUserResize = false;
CanUserSort = false;
}
}

p161 of Chris Andersen's Essential Windows Presentation Foundation pretty much answers this question. If you have it, I recommend it as a reference.
However, you are so close I am not sure how you missed it :)
private static void OnDateChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {
var col = (DataGridTextColumn) target;
var date = (DateTime) e.NewValue;
var textblock = new TextBlock();
col.Header = textblock;
textblock.Text = date.ToString(Strings.ShortDayOfWeekFormat);
textblock.ToolTip = "It is that easy. :)";
}

Give your TextBlock a name with the x:Name attribute and you should be able to access it in code by that name.
<TextBlock x:Name="textBlock1" />

Related

Set the content of a TextBlock

Say I have a TextBlock that looks like this:
<TextBlock TextWrapping="Wrap" >
Text in <Run Foreground="Red">red</Run> and <Run Foreground="Red">more</Run>
</TextBlock>
That works fine and the text is shown in red as you think it would. However, in my real example this text:
Text in <Run Foreground="Red">red</Run> and <Run Foreground="Red">more</Run>
Comes from a service call. (Including the tags.)
Is there a way to supply this text to the TextBlock at runtime?
In fact you have to convert all the input text into Inlines collection (of type TextElementCollection). However doing so requires parsing the input text yourself which is not easy. So why not use the existing parser supported by XamlReader. By using this class, you can parse a XAML code to get an instance of a TextBlock from which you can get the Inlines collection and add that for your actual TextBlock. Here is the code:
var text = "<TextBlock>Text in <Run Foreground=\"Red\">red</Run> and <Run Foreground=\"Red\">more</Run></TextBlock>";
var pc = new System.Windows.Markup.ParserContext();
pc.XmlnsDictionary[""] = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
pc.XmlnsDictionary["x"] = "http://schemas.microsoft.com/winfx/2006/xaml";
var tbl = System.Windows.Markup.XamlReader.Parse(text, pc) as TextBlock;
yourTextBlock.Inlines.Clear();
yourTextBlock.Inlines.AddRange(tbl.Inlines.ToList());
Your actual input text should be wrapped in the pair <TextBlock> & </TextBlock> before being used by the next code.
I thought I would post the control that I made from King Kong's answer. (Just in case it is useful to someone else.)
public class FormatBindableTextBlock : TextBlock
{
private static readonly ParserContext parserContext;
static FormatBindableTextBlock()
{
parserContext = new System.Windows.Markup.ParserContext();
parserContext.XmlnsDictionary[""] = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
parserContext.XmlnsDictionary["x"] = "http://schemas.microsoft.com/winfx/2006/xaml";
}
public static readonly DependencyProperty FormattedContentProperty =
DependencyProperty.Register("FormattedContent", typeof(string), typeof(FormatBindableTextBlock), new UIPropertyMetadata(null, OnFormattedContentPropertyChanged));
public string FormattedContent
{
get { return (string)GetValue(FormattedContentProperty); }
set { SetValue(FormattedContentProperty, value); }
}
private static void OnFormattedContentPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
FormatBindableTextBlock textBlock = sender as FormatBindableTextBlock;
if (textBlock == null)
return;
string newFormattedContent = e.NewValue as string;
if (string.IsNullOrEmpty(newFormattedContent))
newFormattedContent = "";
newFormattedContent = "<TextBlock>" + newFormattedContent + "</TextBlock>";
TextBlock tbl = System.Windows.Markup.XamlReader.Parse(newFormattedContent, parserContext) as TextBlock;
textBlock.Inlines.Clear();
textBlock.Inlines.AddRange(tbl.Inlines.ToList());
}
}

Mixed font types in resx file for localization

I am doing some localization and have bumped into a problem that I cant seem to figure out.
I need the following displayed: tcpCO₂ (where tcp is in italic)
<Italic>tcp</Italic> CO₂
I thought I could just put the HTML in my .resx file and all would be marry, but the content of the output shows the html including brackets etc. Does anyone have inputs in this matter?
I use an attached behavior to display rich XAML text in a TextBlock:
public static class TextBlockBehavior
{
[AttachedPropertyBrowsableForType(typeof(TextBlock))]
public static string GetRichText(TextBlock textBlock)
{
return (string)textBlock.GetValue(RichTextProperty);
}
public static void SetRichText(TextBlock textBlock, string value)
{
textBlock.SetValue(RichTextProperty, value);
}
public static readonly DependencyProperty RichTextProperty =
DependencyProperty.RegisterAttached(
"RichText",
typeof(string),
typeof(TextBlockBehavior),
new UIPropertyMetadata(
null,
RichTextChanged));
private static void RichTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
TextBlock textBlock = o as TextBlock;
if (textBlock == null)
return;
var newValue = (string)e.NewValue;
textBlock.Inlines.Clear();
if (newValue != null)
AddRichText(textBlock, newValue);
}
private static void AddRichText(TextBlock textBlock, string richText)
{
string xaml = string.Format(#"<Span>{0}</Span>", richText);
ParserContext context = new ParserContext();
context.XmlnsDictionary.Add(string.Empty, "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
var content = (Span)XamlReader.Parse(xaml, context);
textBlock.Inlines.Add(content);
}
}
You can use it like this:
<TextBlock bhv:TextBlockBehavior.RichText="{x:Static Resources:Resource.CO2}">
However, in your case don't have access to the TextBlock directly, since it's in the caption template of the LayoutPanel. So you need to redefine this template to apply the behavior on the TextBlock:
<dxdo:LayoutPanel Name="PanelCo2" Caption="{x:Static Resources:Resource.CO2}">
<dxdo:LayoutPanel.CaptionTemplate>
<DataTemplate>
<TextBlock bhv:TextBlockBehavior.RichText="{Binding}">
</DataTemplate>
</dxdo:LayoutPanel.CaptionTemplate>
</dxdo:LayoutPanel>

Databinding TextBlock Runs in Silverlight / WP7

I'm using Silverlight on Windows Phone 7.
I want to display the first part of some text in a TextBlock in bold, and the rest in normal font. The complete text must wrap. I want the bolded part to contain text from one property in my ViewModel, and the plain text to contain text from a different property.
The TextBlock is defined in a DataTemplate associated with a LongListSelector.
My initial attempt was:
<TextBlock TextWrapping="Wrap">
<TextBlock.Inlines>
<Run Text="{Binding Property1}" FontWeight="Bold"/>
<Run Text="{Binding Property2}"/>
</TextBlock.Inlines>
</TextBlock>
This fails at runtime with the spectacularly unhelpful "AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR". This is a known issue because the Run element is not a FrameworkElement and cannot be bound.
My next attempt was to put placeholders in place, and then update them in code:
<TextBlock Loaded="TextBlockLoaded" TextWrapping="Wrap">
<TextBlock.Inlines>
<Run FontWeight="Bold">Placeholder1</Run>
<Run>Placeholder2</Run>
</TextBlock.Inlines>
</TextBlock>
In the code-behind (yes I am desparate!):
private void TextBlockLoaded(object sender, RoutedEventArgs e)
{
var textBlock = (TextBlock)sender;
var viewModel = (ViewModel)textBlock.DataContext;
var prop1Run = (Run)textBlock.Inlines[0];
var prop2Run = (Run)textBlock.Inlines[1];
prop1Run.Text = viewModel.Property1;
prop2Run.Text = viewModel.Property2;
}
This seemed to work, but because I am using the LongListSelector, although items get recycled, the Loaded codebehind event handler doesn't re-initialize the Runs, so very quickly the wrong text is displayed...
I've looked at using the LongListSelector's Linked event (which I already use to free up images that I display in the list), but I can't see how I can use that to re-initialize the Runs' text properties.
Any help appreciated!
I finally found a solution that works for me.
As I mention in the comment, Paul Stovell's approach would not work.
Instead I used a similar approach to add an attached property to the TextBlock, bound to the TextBlock's DataContext, and attached properties on the runs, indicating which ViewModel properties they should be bound to:
<TextBlock TextWrapping="Wrap"
Views:BindableRuns.Target="{Binding}">
<TextBlock.Inlines>
<Run FontWeight="Bold" Views:BindableRuns.Target="Property1"/>
<Run Views:BindableRuns.Target="Property2"/>
</TextBlock.Inlines>
</TextBlock>
Then in my attached TextBox Target (datacontext) property's changed event, I update the Runs, and subscribe to be notified of changes to the TextBox Target properties. When a TextBox Target property changes, I updated any associated Run's text accordingly.
public static class BindableRuns
{
private static readonly Dictionary<INotifyPropertyChanged, PropertyChangedHandler>
Handlers = new Dictionary<INotifyPropertyChanged, PropertyChangedHandler>();
private static void TargetPropertyPropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
if(!(dependencyObject is TextBlock)) return;
var textBlock = (TextBlock)dependencyObject;
AddHandler(e.NewValue as INotifyPropertyChanged, textBlock);
RemoveHandler(e.OldValue as INotifyPropertyChanged);
InitializeRuns(textBlock, e.NewValue);
}
private static void AddHandler(INotifyPropertyChanged dataContext,
TextBlock textBlock)
{
if (dataContext == null) return;
var propertyChangedHandler = new PropertyChangedHandler(textBlock);
dataContext.PropertyChanged += propertyChangedHandler.PropertyChanged;
Handlers[dataContext] = propertyChangedHandler;
}
private static void RemoveHandler(INotifyPropertyChanged dataContext)
{
if (dataContext == null || !Handlers.ContainsKey(dataContext)) return;
dataContext.PropertyChanged -= Handlers[dataContext].PropertyChanged;
Handlers.Remove(dataContext);
}
private static void InitializeRuns(TextBlock textBlock, object dataContext)
{
if (dataContext == null) return;
var runs = from run in textBlock.Inlines.OfType<Run>()
let propertyName = (string)run.GetValue(TargetProperty)
where propertyName != null
select new { Run = run, PropertyName = propertyName };
foreach (var run in runs)
{
var property = dataContext.GetType().GetProperty(run.PropertyName);
run.Run.Text = (string)property.GetValue(dataContext, null);
}
}
private class PropertyChangedHandler
{
private readonly TextBlock _textBlock;
public PropertyChangedHandler(TextBlock textBlock)
{
_textBlock = textBlock;
}
public void PropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedArgs)
{
var propertyName = propertyChangedArgs.PropertyName;
var run = _textBlock.Inlines.OfType<Run>()
.Where(r => (string) r.GetValue(TargetProperty) == propertyName)
.SingleOrDefault();
if(run == null) return;
var property = sender.GetType().GetProperty(propertyName);
run.Text = (string)property.GetValue(sender, null);
}
}
public static object GetTarget(DependencyObject obj)
{
return obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj,
object value)
{
obj.SetValue(TargetProperty, value);
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target",
typeof(object),
typeof(BindableRuns),
new PropertyMetadata(null,
TargetPropertyPropertyChanged));
}
I suggest you give the BindableRun a try. I've only used it in WPF, but I don't see why it wouldn't work in Silverlight.

WPF- Problem with TextBox in DataTemplate

I am working on an application where Repository objects are displayed via a DataTemplate that contains a modified version of a TextBox, which supports binding to the SelectionStart, SelectionLength, and VerticalOffset.
The DataTemplate looks like this:
<DataTemplate DataType="{x:Type m:Repository}">
<controls:ModdedTextBox
x:Name="textBox" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
BindableSelectionStart="{Binding SelectionStart, UpdateSourceTrigger=PropertyChanged}"
BindableSelectionLength="{Binding SelectionLength, UpdateSourceTrigger=PropertyChanged}"
BindableVerticalOffset="{Binding VerticalOffset, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
The problem is that when I change the Repositorythat is currently being displayed; the SelectionStart, SelectionLength, and VerticalOffset all seem to be getting set to 0, even when those properties of the Repository object are not 0.
I think that this is happening in the instant before the text is displayed when the SelectionStart, SelectionLength, and VerticalOffset can not be more than 0. This does not only set the actual properties of the TextBox to zero, but also updates the bindings and sets the properties of the Repository object to zero.
Is there any way that I can prevent this from happening?
--Edit--
I don't know if posting dl links to projects is a no-no or not on SO, but here is a link to a project I created to demonstrate the problem I am having: http://dl.dropbox.com/u/1520079/RepositoryProblemDemo.zip
When you run the demo-app the selection you can click the "Switch Repository" button to change the repository that is displayed in the textbox. If you look to the right of the textbox the current repository's properties all get set to zero when you switch to the other one.
A difference between this demo and my actual app is that in my app repositories will be switched via hotkeys, not a button.
The problem is due to the fact that the bindings are evaluated in serial, and when the Text property is changed it causes all selection information to be removed (you can see this by putting breakpoints on your ModdedTextBox event handlers). As the BindableSelection... bindings are still active at that point, it causes the selection information to be reset.
Depending on the exact behaviour you want there is probably a way to work around this, but you would need to know a little more detail...
Edit in response to comments:
This solution isn't exactly answering your original question, and it probably isn't great practice, but it does at least work...
Try altering your ModdedTextBox so that instead of exposing bindable properties for the selection information, expose a single DP of type Repository and bind to that:
<local:ModdedTextBox
x:Name="textBox"
Repository="{Binding CurrentRepository}"
TextWrapping="Wrap"
/>
Then handle the changed event on your DP to set the text box properties:
public static DependencyProperty RepositoryProperty =
DependencyProperty.Register("Repository",
typeof(Repository), typeof(ModdedTextBox), new PropertyMetadata(null, OnRepositoryChanged));
public Repository Repository
{
get { return (Repository)base.GetValue(RepositoryProperty); }
set { base.SetValue(RepositoryProperty, value); }
}
private static void OnRepositoryChanged(DependencyObject senderObject, DependencyPropertyChangedEventArgs e)
{
var sender = (ModdedTextBox)senderObject;
var oldRepository = e.OldValue as Repository;
var newRepository = e.NewValue as Repository;
if (oldRepository != null)
{
oldRepository.Text = sender.Text;
oldRepository.SelectionStart = sender.SelectionStart;
//etc
}
if (newRepository != null)
{
sender.Text = newRepository.Text;
sender.SelectionStart = newRepository.SelectionStart;
//etc
}
}
This is essentially removing the serial nature of the binding evaluation.
Note: You could also achieve the same using attached properties, which would be better than subclassing TextBox, but this is closer to your original attempts so I figure its easier to explain!
Here is a re-write of the other solution. This one takes into account the text property not being bound before the other properties.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class SelectionBindingTextBox : TextBox
{
public static readonly DependencyProperty BindableSelectionStartProperty =
DependencyProperty.Register(
"BindableSelectionStart",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionStartChanged));
public static readonly DependencyProperty BindableSelectionLengthProperty =
DependencyProperty.Register(
"BindableSelectionLength",
typeof(int),
typeof(SelectionBindingTextBox),
new PropertyMetadata(OnBindableSelectionLengthChanged));
private bool isBindingComplete = false;
public SelectionBindingTextBox()
: base()
{
this.SelectionChanged += this.OnSelectionChanged;
this.TextChanged += this.OnTextChanged;
}
public int BindableSelectionStart
{
get
{
return (int)this.GetValue(BindableSelectionStartProperty);
}
set
{
this.SetValue(BindableSelectionStartProperty, value);
}
}
public int BindableSelectionLength
{
get
{
return (int)this.GetValue(BindableSelectionLengthProperty);
}
set
{
this.SetValue(BindableSelectionLengthProperty, value);
}
}
private static void OnBindableSelectionStartChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (textBox.isBindingComplete)
{
textBox.SetupSelection();
}
}
private static void OnBindableSelectionLengthChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var textBox = dependencyObject as SelectionBindingTextBox;
if (textBox.isBindingComplete)
{
textBox.SetupSelection();
}
}
private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
if (isBindingComplete)
{
this.BindableSelectionStart = this.SelectionStart;
this.BindableSelectionLength = this.SelectionLength;
}
}
private void OnTextChanged(object sender, RoutedEventArgs e)
{
if (!isBindingComplete)
{
SetupSelection();
}
isBindingComplete = true;
}
private void SetupSelection()
{
// this.Focus();
this.SelectionLength = this.BindableSelectionLength;
this.SelectionStart = this.BindableSelectionStart;
}
}
}
Well update of binding depends on the order in which WPF or Silverlight Engine will evaluate, looks like your SelectionStart and SelectionEnd bindings are updated before Text, so when Text gets changed, SelectionStart and SelectionEnd are both changed back to zero.
The only way is to hook for TextChanged event and refresh the bindings of SelectionStart and SelectionEnd or in WPF you can extend textbox as follow
public class MyTextBox : TextBox{
protected override OnTextChanged(TextChangedEventArgs e){
BindingExpression be = this.GetBindingExpression(SelectionStartProperty);
if(be!=null){
be.UpdateTarget();
}
be = this.GetBindingExpression(SelectionEndProperty);
if(be!=null){
be.UpdateTarget();
}
be = this.GetBindingExpression(VerticalOffsetProperty);
if(be!=null){
be.UpdateTarget();
}
}
}
Well here there is a trick, you still have to change above logic to fit in your logic because everytime text updates this will update binding, so you have to find out when to refresh these bindings. Because this will consistantly fail to change your textbox's value in runtime as text will modify and selection will goto previous selection only.

What is the least intrusive way to display WPF tooltip on focus?

What is the minimum number of steps required to display a tooltip when the following control gets focus?
<TextBox ToolTip="Hello there!" ... />
I tried the following in GotFocus
private void ..._GotFocus(object sender, RoutedEventArgs e) {
var element = (FrameworkElement)sender;
var tooltip = element.ToolTip;
if (!(tooltip is ToolTip)) {
tooltip = new ToolTip { Content = tooltip };
element.ToolTip = tooltip;
}
((ToolTip)tooltip).IsOpen = true;
}
However, it seems to ignore the ToolTipService.Placement for this control and SystemParameters.ToolTipPopupAnimationKey set up level higher.
How can I make it work and honor all settings that generally work for tooltips (except the timing, obviously)?
I'd build an IsKeyboardFocused binding in the attached property, like this:
public class ShowOnFocusTooltip : DependencyObject
{
public object GetToolTip(...
public void SetToolTip(...
public static readonly DependencyProperty ToolTipProperty = DependencyProperty.RegisterAttached(..., new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
ToolTipService.SetToolTip(obj,
e.NewValue==null ? null :
BuildToolTip(obj, e.NewValue));
}
});
private object BuildToolTip(DependencyObject control, object content)
{
var tooltip = content is ToolTip ? (ToolTip)content : new ToolTip { Content = content };
tooltip.SetBinding(ToolTip.IsOpenProperty,
new Binding("IsKeyboardFocusWithin") { Source = control });
return tooltip;
}
Don't have a Windows machine to test, but I would have thought:
<TextBox x:Name="textBox">
<TextBox.ToolTip>
<ToolTip IsOpen="{Binding IsKeyboardFocusWithin, ElementName=textBox}">
Whatever
</ToolTip>
</TextBox.ToolTip>
</TextBox>

Resources