Span in Contentpresenter not working properly - wpf

I have stumbled upon a small problem and I'm not sure how to avoid it or work around it and whether it's a bug or a "feature".
When rendering a span with text in it, it seems to be disconnected from the logical tree when using a content presenter to render it. It does not bubble IsMouseOver (or probably any event) and Hyperlinks inside the span also won't fire any associated code.
<ContentPresenter>
<ContentPresenter.Content>
<!--Normally this would be a binding, but it behaves the same.-->
<Span>
Test <Hyperlink Click="Hyperlink_OnClick">Testlink</Hyperlink>
</Span>
</ContentPresenter.Content>
</ContentPresenter>
Inspecting the visual tree with Snoop indeed shows that the TextBlock used to display the span does not receive IsMouseOver-Events from it's inline elements while they themselves do indeed register them correctly (when you expand the inline property and navigate to them; they just refuse to pass them on). Also when attaching a message box to the click handler, nothing happens when you click on the link.
<TextBlock Grid.Row="1">
<Span>
Test <Hyperlink Click="Hyperlink_OnClick">Testlink</Hyperlink>
</Span>
</TextBlock>
This one on the other hand works as expected. The IsMouseOver works fine and even the Link works.
The premise of my problem is, that I want to dynamically bind the text of the TextBlock to something. But I can't bind the text-property to a span directly so I'm using a content presenter which does the job (but is broken). Is this a bug or some feature/implication that I'm unaware of? And is there another way to bind a span to something to display it with working event handling & hyperlink clicks?

You could use a converter that returns a TextBlock with the Span added to its Inlines collection:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Span span = value as Span;
TextBlock textBlock = new TextBlock();
textBlock.Inlines.Add(span);
return textBlock;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML:
<ContentPresenter Content="{Binding Span, Converter={StaticResource MyConverter}}" />

Related

How to get a symmetrical TwoWay binding?

While debugging a problem in a WPF application, I have noticed that the TwoWay data binding does not seem to be symmetrical. Here is an example:
<Window x:Class="ConverterTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ConverterTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:TextConverter x:Key="textConverter"/>
</Window.Resources>
<Grid>
<TextBox x:Name="txt1" Height="24" Margin="0" VerticalAlignment="Top"/>
<TextBox x:Name="txt2" Height="24" Margin="0,40,0,0" VerticalAlignment="Top"
Text="{Binding ElementName=txt1, Path=Text,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource textConverter}}"/>
</Grid>
</Window>
The converter looks like this:
public class TextConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().ToUpper();
}
public object ConvertBack(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().ToLower();
}
#endregion
}
When editing inside txt1, only Convert() is called; but when editing inside txt2, first ConvertBack() and then Convert() is called.
In other words, TwoWay binding seems to work like this:
When the source property is updated, the target property is updated.
When the target property is updated, first the source property is updated, and then the target property is updated again.
The MSDN documentation merely says:
Causes changes to either the source property or the target property to automatically update the other.
From that sentence, I would have expected the following behavior:
When the source property is updated, the target property is updated.
When the target property is updated, the source property is updated.
Is there any way to achieve this symmetrical behavior (purely in XAML, without C# programming)?
EDIT: I just have discovered that in WPF 3.5, TwoWay bindings work symmetrical. In WPF 4.0, however, the behavior has changed (as described above).
Well, entirely symmetrical cannot be achieved because:
When the source is updated, the control just has to show the new value, period. Nothing fancy happens.
When the target is updated, before it is written to the source, the change of value can be canceled at a ridiculous amount of places.
For example: validation.
When the source is updated, say, from code, it passed all validation (unless you forgot to check, but that's another problem). The control just displays the new value.
When the target is updated, the value is first converted back in the converter to be manipulable, then coerced, validated and then at last, the source is set.
But as you see, the value could have been modified between the target and the source, so the value is converted again, and finally displayed.
Bonus: to understand where and in which order the value can change, see Dependency Property Value Precedence.

Overlay 2 controls and toggle which one is visible using WPF

This is a general question which will apply to any WPF control.
What I am trying to do is place two controls on top of each other and toggle which is visible.
I.e I want to control the visbility of them such that only one control is visible at one time.
One control will normally be hidden but upon some event will be displayed on top of the other control.
I have tried changing the z order and tried using the visibility property, but while I can make the normally hidden control appear, the normally displayed control is also visible.
E.g. the button below is normally hidden, but upon an a menu item click, for example, the ShowAboutBox property in a viewmodel will be set, changing the visibility property. At which point the button should be visible and not the dockpanel.
<Grid>
<Button Visibility="{Binding ShowAboutBox, Converter={StaticResource BoolToVisConverter}}">
<Button.Content>About My App</Button.Content></Button>
<DockPanel Canvas.ZIndex="0" LastChildFill="True"></DockPanel>
</Grid>
I'm not that experienced in WPF but assuming that this should be quite easy - any suggestions?
EDIT:
The code above shows a mix of techniques I tried. And probably confuses the issue. Most recently I have tried the following to no avail either.
<Grid>
<Button Visibility="{Binding ShowAboutBox, Converter={StaticResource BoolToVisConverter}}">
<Button.Content>About My App</Button.Content></Button>
<DockPanel></DockPanel>
</Grid>
Changing the visibility of the button causes it to display, but the dock panel and its contents are still visbile on top of the button. (the button is shown behind the dockpanel due to the z order).
I guess I could toggle the visibility of the dock panel at the same time (to be the reverse of the button) but I was hoping to avoid that.
I would bind the DockPanel's Visibility to ShowAboutBox as well, but using an inverse converter. I have a bunch of handy little converters like this created for just this type of scenario:
<Grid>
<Button Visibility="{Binding ShowAboutBox, Converter={StaticResource BoolToVisConverter}}">About My App</Button>
<DockPanel Visibility="{Binding ShowAboutBox, Converter={StaticResource BoolToInverseVisConverter}}"></DockPanel>
</Grid>
And the basic converter (could be expanded to support nullables, etc):
public class BooleanToInverseVisibilityConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return (bool) value ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return null;
}
}
Your ZIndex trick isn't working because the button also has a zindex of 0 (since it is first in the collection). You would need to explicitly change the button's ZIndex to somehting higher than 0 for the DockPanel to appear on top of it.
That said, the correct solution here is to just toggle the button's Visibility property between Hidden & Visible, not changing ZIndex at all.
You can use the generic BooleanConverter here and declare True and False value accordingly.

How to implement a table that appears / expands when the "expand" button is clicked?

Is it better to build 2 different pages? Such that when a button is clicked, it leads to another page with the table? Or can we hide the table until the "expand" button is clicked? Is it better to present the table as a listbox? Thanks.
I think a converter is the way to go, like TerrenceJackson said!
In the DataGrid your Binding should look like this:
<CheckBox Content="Expand" Height="72" Name=MyExpandCheckbox IsChecked="False" />
<DataGrid Visibility="{Binding ElementName=MyExpandCheckbox, Path=IsChecked, Converter={StaticResource BoolToVisibleConverter}}"/>
You have to set the Name of your Button/Checkbox/... for the ElementName in the binding for the visibility-property.
Here is an example of a BoolToVisibilityConverter:
public class BoolToVisibilityConverter : IValueConverter
{
#region IValueConverter member
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (Boolean)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
i would'nt do it with two seperate pages.
You can use a simple expander and style it for your purpose. Here's a video how to use the expander: http://silverlight.net/learn/videos/all/silverlight-toolkit-creating-a-toolkit-expander/ and here's a simple tutorial http://www.c-sharpcorner.com/Resources/768/.
You could also use a simple toggle button and bind the visibility of the table (DataGrid or ListBox, whatever you want) with a converter to the checked state of the toggle button. Somehow like this:
<ToggleButton x:Name="myToggleButton" />
<DataGrid Visibility="{Binding ElementName, Path=IsChecked, Converter={StaticResource BoolToVisibleConverter}}"/>
The BoolToVisibleConverter is a class which implements IValueConverter (http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx).
I don't know your exact aim, but I guess the expander approach is good for you.
Hope this helps.
BR,
TJ

WPF StringFormat on Label Content

I want to format my string binding as Amount is X where X is a property bound to a label.
I've seen many examples but the following doesn't work:
<Label Content="{Binding Path=MaxLevelofInvestment,
StringFormat='Amount is {0}'}" />
I've also tried these combinations:
StringFormat=Amount is {0}
StringFormat='Amount is {}{0}'
StringFormat='Amount is \{0\}'
I even tried changing the binding property's datatype to int, stringand double. Nothing seems to work. This is a very common use case but doesn't seem to be supported.
The reason this doesn't work is that the Label.Content property is of type Object, and Binding.StringFormat is only used when binding to a property of type String.
What is happening is:
The Binding is boxing your MaxLevelOfInvestment value and storing it the Label.Content property as a boxed decimal value.
The Label control has a template that includes a ContentPresenter.
Since ContentTemplate is not set, ContentPresenter looks for a DataTemplate defined for the Decimal type. When it finds none, it uses a default template.
The default template used by the ContentPresenter presents strings by using the label's ContentStringFormat property.
Two solutions are possible:
Use Label.ContentStringFormat instead of Binding.StringFormat, or
Use a String property such as TextBlock.Text instead of Label.Content
Here is how to use Label.ContentStringFormat:
<Label Content="{Binding Path=MaxLevelofInvestment}" ContentStringFormat="Amount is {0}" />
Here is how to use a TextBlock:
<TextBlock Text="{Binding Path=MaxLevelofInvestment, StringFormat='Amount is {0}'}" />
Note: For simplicity I omitted one detail in the above explanation: The ContentPresenter actually uses its own Template and StringFormat properties, but during loading these are automatically template-bound to the ContentTemplate and ContentStringFormat properties of the Label, so it seems as if the ContentPresenter is actually using the Label's properties.
Make a universal StringFormatConverter : IValueConverter. Pass your format string as ConverterParameter.
Label Content="{Binding Amount, Converter={...myConverter}, ConverterParameter='Amount is {0}'"
Also, make StringFormatMultiConverter : IMultiValueConverter when you need more than one object in format string, for instance, Completed {0} tasks out of {1}.
I just checked and for some reason it doesn't work with the Label, probably because it uses a ContentPresenter for the Content property internally. You can use a TextBlock instead and that will work. You could also put the TextBlock excerpt below in the content of a Label if you need to inherit styling, behaviour etc.
<TextBlock Text="{Binding Path=MaxLevelofInvestment, StringFormat='Amount is \{0\}'} />
Try using a converter....
<myconverters:MyConverter x:Key="MyConverter"/>
<Label Content="{Binding Path=MaxLevelofInvestment, Converter={StaticResource MyConverter"} />
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return String.Format("Amount is {0}", value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}

ListBox Disabled state in Silverlight 4

So I'm styling a ListBox and I've got to the part where I need to do a greyed out style when the ListBox is disabled. However when I look a the states tab in Blend, there's only Validation States present - no sign of the usual Common States which include the Disabled state.
I tried creating a vanilla project with no custom styles and just a ListBox and the same thing happens. My question is, how do I go about styling a disabled state for a ListBox? Am I missing something obvious??
First tried the simple approach: Edit the ListBoxItem template, rather than the List box. It is the items that are displayed in disabled state, not the listbox.
In blend:
"Edit Additional Templates" > "Edit Generated Item Container (ItemContainerStyle)" > Edit a copy.
As a test I forced the background colour to red in the disabled state (see picture below). The background colour is normally derived from the parent list. The XAML is too big to list here.
An item container in a listbox consists of a grid containing 3 rectangles (to give the border colour effects) and a content container to hold the actual item content.
fillcolor
fillcolor2
contentPresenter
FocusVisualElement
Obvious problem... all the white-space under the items. Bah! Must be a better way.
Now try to change the ListBox template instead:
To change the template of the ListBox itself I thought you might be able to bind the background colour of the scrollviewer within the ListView Template to the IsEnabled property of the control. This would require a custom value converter (to convert the IsEnabled bool? to a Brush object), but they are pretty simple to create.
TemplateBinding does not support a convertor, but I found that you can use a normal binding in a template, if you use a RelativeSource:
<ScrollViewer x:Name="ScrollViewer" BorderBrush="Transparent" BorderThickness="0" Background="{Binding IsEnabled, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Bool2Color}}" Padding="{TemplateBinding Padding}" TabNavigation="{TemplateBinding TabNavigation}">
The result looked like this:
The code for the value convertor is below
public class BoolToColourConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool?)
{
return new SolidColorBrush((value as bool?).Value ? Colors.Red : Colors.Orange);
}
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
ListBox is a nested control.
You probably will have to style the ScrollViewer control that holds the ListBoxItem(s).
The following two links explain how to style a ListBox. They are not a direct answer to your question, but they may give you some insight on how it works.
http://www.codeproject.com/KB/expression/ListBoxStylingSilverlight.aspx
http://www.codeproject.com/KB/expression/ListBoxStylingPart2.aspx

Resources