Overlay 2 controls and toggle which one is visible using WPF - 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.

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

WPF tab control and MVVM selection

I have a TabControl in an MVVM WPF application. It is defined as follows.
<TabControl Style="{StaticResource PortfolioSelectionTabControl}" SelectedItem="{Binding SelectedParameterTab}" >
<TabItem Header="Trades" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailTradeRegion" cal:RegionManager.RegionName="NSDetailTradeRegion" />
</TabItem>
<TabItem Header="Ccy Rates" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailCcyRegion" cal:RegionManager.RegionName="NSDetailCcyRegion" />
</TabItem>
<TabItem Header="Correlations / Shocks" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Name="NSDetailCorrelationRegion" cal:RegionManager.RegionName="NSDetailCorrelationRegion" />
</TabItem>
<TabItem Header="Facility Overrides" Style="{StaticResource PortfolioSelectionTabItem}" IsEnabled="False">
<ContentControl Name="NSDetailFacilityOverrides" cal:RegionManager.RegionName="NSDetailFacilityOverrides" />
</TabItem>
</TabControl>
So each tab item content has its own view associated with it. Each of those views has the MEF [Export] attribute and is associated with the relevant region through view discovery, so the above code is all I need to have the tab control load and switch between them. They all reference the same shared ViewModel object behind them and so all interact seamlessly.
My problem is that when the user navigates to the parent window, I want the tab control to default to the second tab item. That is easy enough to do when the window is first loaded, by specifying in XAML IsSelected="True" in TabItem number 2. It is less easy to do when the user navigates away from the screen and then comes back to it.
I thought about having a SelectedItem={Binding SelectedTabItem} property on the tab control, so I could programmatically set the selected tab in the ViewModel, but the problem is I have no knowledge of the TabItem objects in the ViewModel as they are declared above in the XAML only, so I have no TabItem object to pass to the setter property.
One idea I had was to make the child Views (that form the content of each of the tab items above) have a style on the UserControl level of their XAML, something along the following.
<Style TargetType={x:Type UserControl}>
<Style.Triggers>
<DataTrigger Property="IsSelected" Value="True">
<Setter Property="{ElementName={FindAncestor, Parent, typeof(TabItem)}, Path=IsSelected", Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
I know the findancestor bit isn't correct; I've just put it there to specify my intent, but I am not sure of the exact syntax. Basically for each UserControl to have a trigger that listens to a property on the ViewModel (not sure how I would distinguish each different UserControl as obviously they can't all listen to the same property or they would all select simultaneously when the property is set to True, but having a property for each usercontrol seems ugly) and then finds its parent TabItem container and sets the IsSelected value to true.
Am I on the right track with a solution here? Is it possible to do what I am pondering? Is there a tidier solution?
If you look at the TabControl Class page on MSDN, you'll find a property called SelectedIndex which is an int. Therefore, simply add an int property into your view model and Bind it to the TabControl.SelectedIndex property and then you can select whichever tab you like at any time from the view model:
<TabControl SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
UPDATE >>>
Setting a 'startup' tab is even easier using this method:
In view model:
private int selectedIndex = 2; // Set the field to whichever tab you want to start on
public int SelectedIndex { get; set; } // Implement INotifyPropertyChanged here
Just FYI,
I gone through the same issue where I add tabs dynamically using ObservableCollection source but last added Tab do not get selected.
I have done same changes what Sheridan said to select Tab as per SelectedIndex. Now last added Tab gets selected but it was not getting focused.
So to focus the Tab we have to add set Binding IsAsync property True.
<TabControl ItemsSource="{Binding Workspaces}" Margin="5" SelectedIndex="{Binding TabIndex, Mode=OneWay,UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
The below code sample will create a dynamic tab using MVVM.
XAML
<TabControl Margin="20" x:Name="tabCategory"
ItemsSource="{Binding tabCategory}"
SelectedItem="{Binding SelectedCategory}">
<TabControl.ItemTemplate>
<DataTemplate>
<HeaderedContentControl Header="{Binding TabHeader}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding TabContent}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Modal Class
TabCategoryItem represents each tab item. On two properties, TabHeader will display a tab caption and TabContent contains the content/control to fill in each tab.
Public Class TabCategoryItem
Public Property TabHeader As String
Public Property TabContent As UIElement
End Class
VM Class
Public Class vmClass
Public Property tabCategory As ObjectModel.ObservableCollection(Of TabCategoryItem)
Public Property SelectedCategory As TabCategoryItem
End Class
The below code will fill and bind the content. I am creating two tabs, tab1 and tab2. Both tabs will contain text boxes. You can use any UIelement instead of text boxes.
Dim vm As New vmClass
vm.tabCategory = New ObjectModel.ObservableCollection(Of TabCategoryItem)
'VM.tabCategory colection will create all tabs
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab1", .TabContent = new TextBlock().Text = "My first Tab control1"})
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab2", .TabContent = new TextBlock().Text = "My first Tab control2"})
mywindow.DataContent = vm
The accepted answer is not working with DependencyObject on your ViewModel .
I'm using MVVM with DependencyObject and Just setting the TabControl didn't work for me.The problem I had was the the property was not getting update on the View when I was setting the tab selectedIndex from the ViewModel.
I did set the Mode to be two ways but nothing was working.
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
The ViewModel property "SelectedTab" was getting updated all the time when I navigated between tabs. This was confirming my binding was working properly. Each time I would navigate the tabs both the Get and Set would get called in my ViewModel. But if I try to set the SelectedIndex in the ViewModel it would not update the view.
ie: SelectedTab=0 or SelectedTab=1 etc...
When doing the set from the ViewModel the SelectedTab 'set' method would be called, but the view would never do the 'get'.
All I could find online was example using INotifyPropertyChanged but I do not wish to use that with my ViewModel.
I found the solutions in this page: http://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged
With DependencyObject, you need to register the DependencyProperties. Not for all properties but I guess for a tabcontrol property you need to.
Below my code:
view.xaml
//Not sure below if I need to mention the TwoWay mode
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
ViewModel.cs
public class ViewModel : DependencyObject
{
public static readonly DependencyProperty SelectedTabDP = DependencyProperty.Register("SelectedTab", typeof(int), typeof(ViewModel));
public int SelectedTab
{
get { return (int)GetValue(SelectedTabDP); }
set { SetValue(SelectedTabDP, value); }
}
}
Basically all I had to do was to actually register the dependency property (DependencyProperty) as you can see above.
What made this hard to figure out was that I have a bunch of other Properties on that view and I didn't need to register them like that to make it work two ways. For some reason on the TabControl I had to register the property like I did above.
Hope this help someone else.
Turns out my problem were because my components have names:
x:Name="xxxxxxxx"
Giving names to components at the same time of biding them with DependencyObject seems to be the main cause of all my issues.
In order to improve semantic of my viewmodel and to not work with an int when using code to check for the selected tab, I made some additions to the accepted answer so to use an Enum instead of an int.
These are the steps:
Define an Enum representing the different tabs:
public enum RulesVisibilityMode {
Active,
History
}
Expose the SelectedTab as a property using the enum instead of the int:
public RulesVisibilityMode SelectedTab { get; set; }
Create a converter to convert from an int to your enum (I don't need the ConvertBack because I never select the active tab from the code, but you can add it too):
internal class RulesVisibilityModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("Conversion from visibility mode to selected index has not been implemented");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int selectedTabIndex;
if (int.TryParse(value.ToString(), out selectedTabIndex))
{
return (RulesVisibilityMode)selectedTabIndex;
}
return null;
}
}
Bind the tabcontrol to the SelectedTab property through the converter:
<TabControl SelectedIndex="{Binding SelectedTab, Mode=OneWayToSource, Converter={StaticResource RulesVisibilityModeConverter}}" ...
Now every time you need to check for the selected tab in the code you deal with a readable enum:
if (this.SelectedTab != RulesVisibilityMode.Active) ...

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.

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

How to change a WPF control's visibility from ViewModel

I've an WPF application where tried to implement MVVM pattern and Prism 2. I have a Usercontrol which has subscribed to an event fired from another Usercontrol. I would like to toggle visibility of few child elements in the subscribing control. Events are fired properly, even I am successfully able to bind data to some elements. How do I bind Visibility or any style property for that matter with the ViewModel and change them dynamically.
You can have a boolean property in your ViewModel and bind that property to the Visibility property of your controls. Since you will be asigning a boolean value and the Visibility property is expecting a Visibility enumeration value, you will have to use the BooleanToVisibilityConverter converter to make the conversion,
<Style.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</Style.Resources>
<Image Visibility="{Binding Path=ShowImage,
Converter={StaticResource booleanToVisibilityConverter}}"/>
Hope this helps.
Ezequiel Jadib
Although adding a Boolean property and using a value converter works, I would recommend adding a property of type Visibility to your ViewModel, e.g.
public Visibility ImageVisibility
{
get { return shouldShowImage ? Visibility.Visible : Visibility.Collapsed }
}
The advantage of this method is you don't need to write a converter for every property you want to express in a visual way (e.g. for a stock level that turns a label red when it drops below 10, you could have a converter you use once or just expose a StockLabelBrush property from your VM)
There's a simple solution for people who run into this issue.
In your view model, create a "Visibility" property like so:
public Visibility ShowModifyButtons
{
get { return (Visibility)GetValue(ShowModifyButtonsProperty); }
set { SetValue(ShowModifyButtonsProperty, value); }
}
public static readonly DependencyProperty ShowModifyButtonsProperty =
DependencyProperty.Register("ShowModifyButtons", typeof(Visibility), typeof(FileMatchViewModel),
new UIPropertyMetadata(Visibility.Collapsed));
In your XAML, bind to it like so:
<Button Focusable="False" Content="Save" Width="100" Margin="10" Visibility="{Binding ShowModifyButtons}"/>
Now, from your view model, you can set ShowModifyButtons to Visibility.Collapsed or Visibility.Visible as needed.

Resources