ListBox Disabled state in Silverlight 4 - silverlight

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

Related

Span in Contentpresenter not working properly

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}}" />

ObservableCollection Remove() not firing to Visibility binding

I have a strange issue with my WPF project. I have a ObservableCollection<T> bound to a ListBox. When I add and remove items, the binding works and the list displays the correct results.
The issue I have, is I'm also binding this same property to another XAML control, but it doesn't trigger the converter when I remove an item from the list. It works when I add items.
The relevant XAML is
<view:WelcomeView Visibility="{Binding Steps, Converter={StaticResource CollapseIfZero}}"/>
<ListBox ItemsSource="{Binding Steps}" />
And the converter is
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var col = value as ICollection;
return col.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
I have a break point in the converter. When a new item is added, the break point is hit. When an existing item is removed, the break point is not hit.
Does WPF do something magical with the ListBox which I'm not aware of (which has led to this unexpected behavior)?
ObservableCollection implements INotifyCollectionChanged and ListBox (and other ItemsControls) listens when collection was modified.
Steps property itself doesn't change, it is the same ObservableCollection.
WelcomeView.Visibility is bound to Steps, and doesn't update because property value didn't change, it keeps the same object reference.
try create binding to Steps.Count property (converter should be modified to use int value)
<view:WelcomeView Visibility="{Binding Steps.Count, Converter={StaticResource CollapseIfZeroCount}}"/>
or
there is bool HasItems property in ItemsControl. I would make a binding with ElementName and BooleanToVisibilityConverter
<view:WelcomeView "{Binding ElementName=Lst, Path=HasItems, Converter={StaticResource Bool2Visibility}}"/>
<ListBox Name="Lst" ItemsSource="{Binding Steps}" />

How to Highlight Cell in Silverlight DataGrid

How can I programmatically highlight a DataGrid cell in Silverlight?
You'll need to do the following:
Add a property (let's name it IsSelectedInChart) to your data item class. This property must be public and must raise the INotifyPropertyChanged.PropertyChanged event whenever its value changes.
In your lineseries_SelectionChanged you should find the data item that corresponds to the selected point and set IsSelectedInChart to true for it and to false for others.
Make all instances of DataGridRow that exist in the DataGrid have a Binding set to their Background property with Path=IsSelectedInChart and a custom `IValueConverter'.
The converter should look like this:
public class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// TODO: be more careful with nulls and non-expected values
bool isSelected = (bool)value;
return isSelected ? new SolidColorBrush(Colors.Red) : DependencyProperty.UnsetValue;
}
}
The last step is the trickiest. It may be implemented by overriding the DataGridRow Style. One approach is shown in https://stackoverflow.com/a/4268159/795861, another one in https://stackoverflow.com/a/3542179/795861. Check those out.
All these steps are required since you're likely to have a lot of rows in the DataGrid. It uses UI virtualization which makes it impossible to simple set the Background property on the required DataGridRow because a single row object is used to present multiple data items. Thus, the only way to make it work with scrolling is binding the background to the data items.
UPDATE
To highlight cells in a column that is known at design time, set DataGridColumn.CellStyle property to that column definition instead of setting the row style:
<sdk:DataGrid>
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn x:Name="theColumnToHighlight">
<sdk:DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type sdk:DataGridCell}">
<Setter Property="local:SetterValueBindingHelper.PropertyBinding">
<Setter.Value>
<local:SetterValueBindingHelper
Type="System.Windows.Controls.Control, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
Property="Background"
Binding="{Binding IsSelectedInChart, Converter={StaticResource highlighterConverter}}"/>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGridTextColumn.CellStyle>
</sdk:DataGridTextColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Should work, although I haven't tried. The xaml does the same as I've suggested for highlighting entire rows, but applies it to cells in a specific column.
you can use this code according to your cell template in DataGrid.
the cellContent give you reference to the cell you want to modify.
FrameworkElement cellContent = dataGrid.Columns[0].GetCellContent(dataRow);// datarow is your row where cell intersects.
cellContent .Style = s; // assuming s is the style you want to apply

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 make a WPF content conditional on some bound data?

I want to create a menu item but the displayed text depends on a property of the view model.
If the property IsPlaying is true, the MenuItem text should be "Pause", else it should be "Play".
Without this condition, the MenuItem should be something like:
<MenuItem Header="_Play" Command="{Binding Path=PlayCommand}" />
But, "Play" and "Pause" should interchange (and if possible PlayCommand should interchange with PauseCommand too, but this can be worked by having both the logic of PlayCommand and PauseCommand in PlayCommand)
The simplest way to do this is first you should bind the Header to a string Caption property in your viewmodel which returns Play or Pause based on the value of IsPlaying and implement INotifyPropertyChanged. After this, just throw change notification for Caption also when IsPlaying is changed.
Although you can use a converter, but in this case it will be an overkill.
A couple of ways to do this:
Use a Trigger. Set a Trigger on IsPlaying = True, and set the Header and Command to Pause and PauseCommand respectively.
Have two menu items, Play and Pause, and use a pair of triggers to set their Visibility according to IsPlaying. (You could also data-bind Visibility, but using triggers avoids the need to define a BooleanToInvisibilityConverter.)
The best thing for this is a converter. Your code will look something like this:
<UserControl xmlns:myConverters="MyRandomNamespace">
<UserControl.Resources>
<myConverters:MyMenuTextConverter x:Key="MyMenuTextConverter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<TextBlock Text="{Binding IsPlaying, Converter={StaticResource MyMenuTextConverter }}" />
</Grid>
</UserControl>
and in the converter:
namespace MyRandomNamespace
{
public class MyMenuTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool) value == true)
return "Pause";
return "Play";
}
}
}
I've used a TextBlock to display the concept behind the binding, all you have to do is use the same binding syntax on the appropriate property of the MenuItem. I'm also returning literal text from the converter which is not optimal (personally i like my text converters to retrieve their values from a string resource file so that my app is culture aware), but you get the idea.
In WPF you can use a DataTrigger to change the content based on state in your viewmodel (you could even use this technique to swap out the template). Another alternative is to use the VisualStateManager (the distant cousin of datatriggers created for Silverlight's absence thereof that was then backported to WPF as well) to do a similar change from one state (IsPlaying) to the next (!IsPlaying).
I would love to give a more detailed example but it's past my bedtime. Maybe later today.

Resources