Closing a Popup when its parent gets collapsed - silverlight

I've been struggling with this for quite some time and I can't seem to find a proper solution. This is the scenario stripped down. Imagine you have the following XAML:
<Grid x:Name="LayoutRoot" Background="White">
<Grid x:Name="Host" Width="200" Height="200">
<Popup IsOpen="True">
<Button Content="Some Button" Click="Button_Click" />
</Popup>
</Grid>
</Grid>
In the Button_Click event handler all I do is collapse the grid with name Host.
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Host.Visibility = System.Windows.Visibility.Collapsed;
}
What I expected was that the Popup would close therefore hiding the Button. I understand that Popups are not in the same VisualTree and I understand why this might not be working the way I expect it to, but there should be some kind of mechanism for this to happen automatically. The only workaround that comes to my mind is on LayoutUpdated to traverse the visual tree up and ask each Button's parent if it is visible and if I meet a collapsed parent -> close the Popup. However, imagine the performance hit having a HUGE visual tree. It's insane to traverse the visual tree on every layout pass.
I'm open to any sort of suggestions.
EDIT: It seems that I did not explain fully my scenario. The case is to collapse the Popup if ANY of its parent gets collapsed (not just the immediate one). In WPF there is a useful property called IsVisible which is different than Visibility. For example, Visibility might still be Visible, but IsVisible will be false in this scenario.
Best Regards,
K

I think you found a bug, or at least a "weirdness" in the popup control - check this out:
My initial thought was to simply Bind the Visibility of the Popup to the Host. This SHOULD work.
<Grid x:Name="LayoutRoot" Background="White">
<Grid x:Name="Host" Width="200" Height="200" Background="Aqua">
<Popup IsOpen="True" Visibility="{Binding ElementName=Host, Path=Visibility}" Height="100" Width="100">
<Button Content="Some Button" Click="Button_Click"/>
</Popup>
</Grid>
</Grid>
But it does not work. The "Host" grid disappears, but I still see the button. This confused me, so I fired up Silverlight Spy, and check this out - setting the Visibility of the Popup does NOT hide the button!
See Demo Video
Anyway, in order to make this work, you just need to massage things a little bit in order tie the Host Visibility to the IsOpen property. I used a converter here:
<UserControl.Resources>
<Junk:VisibilityToBoolConverter x:Key="VisibilityToBoolConverter"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Grid x:Name="Host" Width="200" Height="200" Background="Aqua">
<Popup IsOpen="{Binding ElementName=Host, Path=Visibility, Converter={StaticResource VisibilityToBoolConverter}}" Height="100" Width="100">
<Button Content="Some Button" Click="Button_Click"/>
</Popup>
</Grid>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Host.Visibility = System.Windows.Visibility.Collapsed;
}
public class VisibilityToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((Visibility) value) == Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
That's working for me.

If you just want to close the popup, why don't you set the IsOpen at the popup to false.
<Grid x:Name="LayoutRoot" Background="White">
<Grid x:Name="Host" Width="200" Height="200">
<Popup x:Name="HostPopup" IsOpen="True">
<Button Content="Some Button" Click="Button_Click" />
</Popup>
</Grid>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
this.HostPopup.IsOpen = false;
}
This closes the popup.

Related

Access keys not working inside popup (WPF)

I have a simple popup in my WPF application.
There is a button with access-key inside this popup.
The problem I have is that this button doesn't respond to Alt+access-key combination.
Moreover pressing Alt doesn't make access key visible like it happens in ordinary window.
Is there any way to make controls inside popup respond to Alt+access-key combination?
P.S. I have no problem with navigation using Tab through this popup.
Sapmle code that I'm using
<Grid>
<Button Click="ButtonBase_OnClick" Content="_Open File"></Button>
<Popup x:Name="Popup" StaysOpen="False">
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="_Open File"/>
<Button Grid.Row="1" Content="O_pen File"/>
<CheckBox Grid.Row="2" Content="_Go"></CheckBox>
</Grid>
</Popup>
</Grid>
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Popup.IsOpen = true;
}
I have also tried adding this as the first answer suggests
private void Popup_OnOpened(object sender, EventArgs e)
{
var popup = sender as Popup;
popup.Child.Focusable = true;
Keyboard.Focus(popup.Child);
}
I have also tried the idea from the first comment
private void Popup_OnOpened(object sender, EventArgs e)
{
var popup = sender as Popup;
FocusManager.SetIsFocusScope(popup, true);
}
or instead of standart focus scope the one from suggested link codeproject.com/Articles/38507/Using-the-WPF-FocusScope
EnhancedFocusScope.SetFocusOnActiveElementInScope(popup);
Setting focus scope helped a little bit, but I didn't manage to make it work exactly as I would like.
Setting focus scope to true did help to use alt+key combination for checkboxes and label+textbox, but not for buttons. Although I could use Alt+access key combinations, I couldn't actually see them, because underscores didn't appear when I pressed Alt
Popup is not part of the visual tree. This means it has its own isolated focus scope. When a Popup is opened, the Popup.Child is hosted in a dedicated Window with its own detached visual tree. The Popup.Child therefore needs to explicitly receive keyboard focus before the access keys are available in the detached focus scope.
You can handle the Popup.Opened event, either in code-behind or using an attached behavior.
It's essential that the Popup.Child is focusable in order to receive keyboard focus.
Some classes like Panel and its subclasses have UIElement.Focusable set to false by default.
<StackPanel>
<ToggleButton x:Name="ToggleButton" Content="Show Popup" />
<Popup x:Name="Popup"
AllowsTransparency="True"
PlacementTarget="{Binding ElementName=ToggleButton}"
IsOpen="{Binding ElementName=ToggleButton, Path=IsChecked}"
Opened="Popup_OnOpened">
<StackPanel>
<Button Grid.Row="1" Content="O_pen File" />
<CheckBox Grid.Row="2" Content="_Go" />
</StackPanel>
</Popup>
</StackPanel>
private void Popup_OnOpened(object sender, EventArgs e)
{
var popup = sender as Popup;
popup.Child.Focusable = true;
Keyboard.Focus(popup.Child);
}
Once a button or a checkbox inside the popup is focused, the alt-shortcuts works.
<Button Click="ButtonBase_OnClick" Content="_Open File" />
<Popup x:Name="Popup">
<StackPanel Background="White">
<CheckBox x:Name="FirstCeckbox" Content="_Foo" />
<CheckBox Content="_Bar" />
</StackPanel>
</Popup>
Code behind:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Popup.IsOpen = !Popup.IsOpen;
if (Popup.IsOpen)
FirstCeckbox.Focus(); // Make sure to focus a Button or a Checkbox, not the Stackpanel or Grid etc.
}

XAML collapse all child elements

I am working on a WPF Xaml application. The app has various stackpanels (that behave like icons) that I need to change the Visibility based on certain criteria.
Question:
How can I collapse all child elements (stackpanels)?
I am collapsing each one by one in the backend in vb.net. But much rather find a cool way to do it all at once.
In that case you can have two options
you can achieve it through style(This will not work if your following MVVM, that is if your binding)
Create coustom control
With Style:
Write the style as below with the target type which is used to display the image
<Window.Resources>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Height="26" Width="200" Name="text1"/>
<TextBox Height="26" Width="200" Name="text2"/>
<Button Height="26" Width="200" Click="Button_Click_2" />
</StackPanel>
</Grid>
TextBox by default will be collapsed you can make it visible based on the search in the backend code
Custom control:
This will be just a wrapper for what ever control your using to display the icon but with only one change is that the default visibility will be collapsed. Then you can make it visible which ever you want
Override hide the already existing Visibility the property with the default value collapsed
You can set the visibility to parent instead of setting it to each control
For example
<StackPanel>
<StackPanel Name="pane1">
<Button Height="30" Width="200" Content="one" Click="Button_Click" />
</StackPanel>
<StackPanel Name="panel2">
<Button Height="30" Width="200" Content="two" Click="Button_Click_1" />
</StackPanel>
</StackPanel>
In you back end write the logic to set the visibility for stackpanel instead of each control
private void Button_Click(object sender, RoutedEventArgs e)
{
pane1.Visibility = Visibility.Collapsed;
panel2.Visibility = Visibility.Visible;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
panel2.Visibility = Visibility.Collapsed;
pane1.Visibility = Visibility.Visible;
}
If your following mvvm then bind to the visibility property of the stack panel like below
<StackPanel>
<StackPanel Visibility="{Binding CanShowPanel1}">
<Button Height="30" Width="200" Content="one" Click="Button_Click" />
</StackPanel>
<StackPanel Visibility="{Binding CanShowPanel2}">
<Button Height="30" Width="200" Content="two" Click="Button_Click_1" />
</StackPanel>
</StackPanel>
Bind them using an IValueConverter implementation like BooleanToVisibilityConverter.
If that isn't good enough, you will have to do them one by one. Maybe write a custom behavior?
The easiest way to do that is to use IValueConverter with parameter.
<StackPanel>
<StackPanel Visibility="{Binding TheQuery,Converter={StaticResource QueryConverter,ConverterParameter="MyFirstStackPanelVisible"}}">
</StackPanel>
<StackPanel Visibility="{Binding TheQuery,Converter={StaticResource QueryConverter,ConverterParameter="MySecondStackPanelVisible"}}">
</StackPanel>
</StackPanel>
public class QueryConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch((string)parameter)
{
case "MyFirstStackPanelVisible"
bool result =CheckQueryCriteriaForFirstStackPanel();
return Visibility.Visible or Visibility.Collapsed;
case "MySecondStackPanelVisible"
bool result =CheckQueryCriteriaForSecondStackPanel();
return Visibility.Visible or Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Of course you shouldn't make CheckQueryCriteriaFor... function for every case.
This approach guarantee logic to be in one place.

How to change button text from OK/CANCEL to YES/NO for MessageBox in SL4?

SL 4 provides a dialog box by MessageBox, but MessageBoxButton only provide option for button as OK, Cancel. How to change it to YES, NO button?
This MessageBox built into silverlight can't be changed beyond the capabilities that are exposed.
Your only solution would be to make a custom ChildWindow class which provides the functionality you want. There are many examples of this.
This has the advantage of acting more like other silverlight popup windows, and can be themed and skinned however you'd like, with whatever buttons and functionality you chose to implement.
This has the disadvantage that you are forced then to use a callback model rather than an a more usual imperative flow control.
Your best bet is to use the System.Windows.Controls.Primitives.Popup
<Grid x:Name="LayoutRoot" Background="White">
<Button x:Name="showPopup" Click="showPopup_Click" Height="100" Width="100" Content="Show Popup"/>
<Popup x:Name="myPopup" IsOpen="False" VerticalAlignment="Top" HorizontalAlignment="Center" >
<Canvas Height="200" Width="300" Background="Azure">
<Button x:Name="closePopup" Click="closePopup_Click" Height="50" Width="100" Content="Close Popup"/>
</Canvas>
</Popup>
<Canvas x:Name="myCanvas" Visibility="Collapsed" Background="Black" Opacity=".4"></Canvas>
</Grid>
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
}
private void closePopup_Click(object sender, RoutedEventArgs e)
{
myPopup.IsOpen = false;
myCanvas.Visibility = Visibility.Collapsed;
}
private void showPopup_Click(object sender, RoutedEventArgs e)
{
myPopup.IsOpen = true;
myCanvas.Visibility = Visibility.Visible;
}
}
If you don't want to create your own popup, there probably are 3rd party messageboxes, but with this solution, you have eveything in your own hands.

ToggleButton not unchecking when clicked

I have a ToggleButton that is malfunctioning. As I understand it, a ToggleButton should go checked when clicked then unchecked when clicked again.
The ToggleButton in this example does not. Clicking it just sets it to checked again. Any Ideas why?
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ToggleButton Width="100" Height="35" Name="btnAddLinkComment" >
<CheckBox Content=" Comment" FlowDirection="RightToLeft" IsHitTestVisible="False"
Focusable="False" IsChecked="{Binding ElementName=txtLinkComment, Path=Text}"
Name="chkHasComment" Margin="5"/>
</ToggleButton>
<Popup IsOpen="{Binding ElementName=btnAddLinkComment,Path=IsChecked}"
PlacementTarget="{Binding ElementName=btnAddLinkComment}" Name="popAddCommentLink"
AllowsTransparency="True" StaysOpen="False" PopupAnimation="Fade" HorizontalOffset="-50"
VerticalOffset="50">
<Border BorderBrush="#FF000000" Background="LightBlue" BorderThickness="1,1,1,1"
CornerRadius="8,8,8,8" Padding="5">
<Grid Background="LightBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="200"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock TextWrapping="Wrap" Foreground="Black">Enter Link Comment:</TextBlock>
<TextBox Grid.Column="1" Name="txtLinkComment" Width="200"></TextBox>
</Grid>
</Border>
</Popup>
</Grid>
</Page>
I guess this happens due to popup being bent to the btnAddLinkComment.isChecked property. I believe what happens is that you're clicking on the button when pop is shown which makes it to close and sets button's IsChecked field to false which puts the button into untoggled state; then the click gets processed by the button itself and since it not toggled it becomes toggled and popup gets shown again. I guess you could resolve the issue by removing the binding and do some handling in code; smth like this:
btnAddLinkComment.Click += btnAddLinkComment_Click;
popAddCommentLink.Closed += popAddCommentLink_Closed;
private void btnAddLinkComment_Click(object sender, RoutedEventArgs e)
{
if (popAddCommentLink.IsOpen && btnAddLinkComment.IsChecked == false)
popAddCommentLink.IsOpen = false;
else if (!popAddCommentLink.IsOpen && btnAddLinkComment.IsChecked == true)
popAddCommentLink.IsOpen = true;
}
private void popAddCommentLink_Closed(object sender, EventArgs e)
{
btnAddLinkComment.IsChecked = false;
}
hope this helps, regards
I am not entirely sure what you want to accomplish but the code below might be a step in the right direction. Please elaborate!
<Window x:Class="ToggleButtonSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:ToggleButtonSpike">
<Window.Resources>
<local:TextToBool x:Key="StringToBool"/>
</Window.Resources>
<StackPanel>
<ToggleButton Name="Toggle" >
<CheckBox IsHitTestVisible="False"
Content="{Binding ElementName=Comment, Path=Text,
UpdateSourceTrigger=PropertyChanged}"
IsChecked="{Binding ElementName=Comment, Path=Text,
Converter={StaticResource StringToBool}}"/>
</ToggleButton>
<Popup IsOpen="{Binding ElementName=Toggle, Path=IsChecked}"
PlacementTarget="{Binding ElementName=Toggle}">
<StackPanel>
<TextBlock Foreground="White">
Enter comment:
</TextBlock>
<TextBox Name="Comment"/>
</StackPanel>
</Popup>
</StackPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Data;
namespace ToggleButtonSpike
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class TextToBool : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !string.IsNullOrEmpty((string)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
When you click on toggle button. It will checked or unchecked, please kindly remember that. At the first click on it, it will be focused.
Please try:
<ToggleButton Focusable="False"/>
Hope to help you

Silverlight: Define event handler in hierarchical data template

I am having problems getting at a click event of a button and am using Silverlight 3.0 w/ matching Silverlight Toolkit.
Problem
I have this TreeView:
TreeView sample http://a.imagehost.org/0939/TreeView.png.
The value for a certain node is the sum of the values of its children. Only in leaves can data be added (for the time being).
What I want to achieve is that a user can add (and eventually remove) entries in the tree to eventually create a custom diagram.
To that end I would like the "plus sign" to insert a new line / node as child of the node where the user clicked. (I.e. if I click the plus at "Display", I get a line below to specify CRT or TFT or whatever.)
Thing is, for all my brain is worth, I don't know how to receive any useful event.
The TextBlock, TextBox and Button are defined in a hierarchical template and I can't define a Click-handler in that template.
OTOH, I haven't found a way to get at the template items of a certain TreeViewItem from within (c#) code. Very well am I able to do trv.ItemContainerGenerator.GetContainerFromItem(item), and as Justin Angel showed I can very well change the font size, but didn't find any way to access the textbox or button.
Is there any way to capture the click event to the button? Or any alternative way of getting something that gives the "add below" functionality?
Thank you in advance.
More Data
The treeview xaml is this:
<controls:TreeView x:Name="SankeyDataTree"
ItemTemplate="{StaticResource SankeyTreeTemplate}" BorderThickness="0"
Background="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top">
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Loading..."/>
</DataTemplate>
</controls:TreeViewItem.HeaderTemplate>
</controls:TreeViewItem>
</controls:TreeView>
I use this HierarchicalDataTemplate (and stole the appraoch from Timmy Kokke):
<Data:HierarchicalDataTemplate x:Key="SankeyTreeTemplate" ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<!-- ... -->
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Value.name, Mode=TwoWay}" VerticalAlignment="Center"/>
<TextBox Text="{Binding Path=Value.flow, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{box\}}"/>
<TextBlock Text="{Binding Path=Value.throughput, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{block\}}"/>
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</Grid>
</Data:HierarchicalDataTemplate>
To this TreeView is bound a "SimpleTree", whose Values hold basically onle a string (name) and two doubles (flow and throughput).
public String name { get; set; }
public Double flow { get; set; }
public Double throughput { get; set; }
(Plus the code for the INotifyPropertyChanged to get a twoway bind to the text boxes.)
You can attach a Behavior to the Button in the HierarchicalDataTemplate and let that handle Click events from the Button.
Download and install the Expression Blend 3 SDK. Add a reference to System.Windows.Interactivity in the project and add a Behavior attached to a Button:
public class ButtonClickBehavior : Behavior<Button> {
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.Click += ButtonClick;
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.Click -= ButtonClick;
}
void ButtonClick(object sender, RoutedEventArgs e) {
Node node = AssociatedObject.DataContext as Node;
if (node != null) {
// Button clicked. Do something to associated node.
}
}
}
Attach the Behavior to the Button in the HierarchicalDataTemplate (assuming this namespace declaration: xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"):
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<interactivity:Interaction.Behaviors>
<local:ButtonClickBehavior/>
</interactivity:Interaction.Behaviors>
</Button>
If desired you can add properties to ButtonClickBehavior and set those from XAML to create a more reusable Behavior.
You can handle the button click event in the code behind. To get to the data, just bind it to the Tag attribute.
<Button Margin="0" Grid.Column="2"
Click="Button_Click" Tag="{Binding}"
Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
In the code behind, handle it and access the element.
private void Button_Click(object sender, RoutedEventArgs e)
{
var data = ((Button)sender).Tag as SimpleTreeNode
}
Where SimpleTreeNode is the name of your tree element class.
You should be able to add a new node to the data found now.

Resources