Popup inside ToolBarOverflow Panel does not work properly - c#

I have a ToolBar in my application which causes Problems.
I have "DropDown" Buttons inside the ToolBar (ToggleButton + Popup) Those DropDowns work properly if they are on the Visible Part of the ToolBar, they do not work properly if they are located in the ToolBarOverflowPanel.
If i Open a DropDown in the ToolBarOverflowPanel the Popup does not seem to receive focus. There are still hover effects (opposing to the behaviour of the same Popup in the visible toolbar which seems to consume all Mouse Events) and i can still click any other DropDown which opens the next Popup while the initial one stays open.
The following code is a full working sample to reproduce the behaviour.
<Window x:Class="ToolbarProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350"
Width="525">
<StackPanel>
<ToolBar ItemsSource="{Binding Items}">
<ToolBar.ItemTemplate>
<DataTemplate >
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal">
<ToggleButton Name="ToggleButton"
ToolTip="ToolTip"
IsChecked="{Binding ElementName=ContextActionPopup, Path=IsOpen, Mode=TwoWay}"
ClickMode="Release">
<TextBlock Text="ICON"/>
</ToggleButton>
<Popup Name="ContextActionPopup"
StaysOpen="False">
<Border x:Name="Border"
Background="White"
Padding="1"
Visibility="Visible">
<TextBlock Text="Content" />
</Border>
</Popup>
</StackPanel>
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
</StackPanel>
namespace ToolbarProblem
{
using System.Collections.Generic;
public partial class MainWindow
{
public MainWindow()
{
this.InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public List<object> Items { get; } = new List<object>
{
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object(),
new object()
};
}
}
I did tried the following approaches without any success:
Add some code to call Popup.Focus
Change ToggleButton.ClickMode to everything possible
Setting the Popup.StaysOpen property to true does seem to work, but is
of course inappropriate for my target

I am afraid that your XAML will never work properly. You can find the reason in the Popup control code. If you set the StaysOpen property to false, the Popup when opening, will call this private method (just use ILSpy to inspect):
private void EstablishPopupCapture()
{
if (!this._cacheValid[1] && this._popupRoot.Value != null && !this.StaysOpen && Mouse.Captured == null)
{
Mouse.Capture(this._popupRoot.Value, CaptureMode.SubTree);
this._cacheValid[1] = true;
}
}
So if there is no other control that is capturing mouse events (i.e. Mouse.Captured == null) your popup will capture them
to determine when one of these events occurs outside the Popup control.
as MSDN remarks. Please, note that Capture method and Captured properties are static, so just one control at a time can capture by using Mouse class.
Now just take a look to the Toolbar default style: you will find that its Popup control, named "OverflowPopup", has its StaysOpen property set to false.
So when you click on the overflow thumb, the "OverflowPopup" calls the EstablishPopupCapture while it is opening. In this case Mouse.Captured is null so it can capture mouse events.
After a while you click on a ToggleButton which is inside the "OverflowPopup" (so it will continue to stay open). Now your Popup - while opening - calls EstablishPopupCapture, but this time Mouse.Captured is not null. So it is not able to listen to mouse events by using Mouse class.
I guess you should consider changing your XAML or adopting a different solution (for example you can write your own template for your Toolbars in order to set OverflowPopup's StaysOpen property to true).
This is a simplified explanation, but the fact of the matter is that it is not possible to have two or more opened popups with StaysOpen set to false: they simply cannot work as expected. Just try to run this XAML:
<StackPanel Orientation="Horizontal">
<Button Content="I am button 1" Name="btn1" Height="20" />
<Button Content="I am button 2" Name="btn2" Height="20" />
<Popup StaysOpen="False" IsOpen="True" PlacementTarget="{Binding ElementName=btn1}">
<TextBlock Text="Popup1" Margin="6" Background="Red" />
</Popup>
<Popup StaysOpen="False" IsOpen="True" PlacementTarget="{Binding ElementName=btn2}">
<TextBlock Text="Popup2" Margin="6" Background="Red" />
</Popup>
</StackPanel>
And you will see that Popup2 will not work as it should do.

Related

How to handle ItemsControl size change correctly to show/collapse scroll buttons

I build a user control that should show a list of status indicators at the bottom of our application. I use an ItemsControl to create the list and enabled it to scroll horizontally via mouse wheel but also added two buttons (Left and Right) to replace the usual scroll bar (because the scrollbar would conflict with the look and feel and would be to big in this situation).
If there are not enough Items in the List or the control is wide enough to show all items, I want to collapse the buttons and of course show them again as soon as scrolling is needed.
Currently I use the ScrollViewer's SizeChanged event to detect if the width has changed and if any items of the ItemsControl are not visible so it needs scrolling. (See C# Code at the bottom of the post.)
My Issue is that it works fine as long as I change the size by gabbing the edge of the window with the mouse and resize it that way but it does not work as soon as the window size is programmaticaly changed or by double clicking on the window title to make it full screen (or use the maximize/minimize button).
This is my UserControl:
<Grid x:Name="grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<dx:SimpleButton x:Name="scroll_left_button" Grid.Column="0" Content="←"/>
<Border Background="#FF00ACDC" Grid.Column="1" BorderThickness="0">
<ItemsControl x:Name="items_control" w3ClientUi:ScrollViewerHelper.UseHorizontalScrolling="True"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.CanContentScroll="True" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer HorizontalScrollBarVisibility="Hidden">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- dummy template -->
<StackPanel.ToolTip>
<TextBlock Text="{Binding Name}"/>
</StackPanel.ToolTip>
<panda:PndLabel Padding="0 10 2 10" Content="{Binding Number}" FontSize="16" FontWeight="Normal"
Foreground="White" Margin="0 0 2 0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- ItemsSource is set in Code -->
</ItemsControl>
</Border>
<dx:SimpleButton Grid.Column="2" x:Name="scroll_right_button" Content="→"/>
</Grid>
In Code behind I get the ScrollViewer instance from the ItemsControl as soon as it's loaded and then attach to the SizeChanged event.
When changing the window size via the window title or programmaticaly the event is thrown but the ScrollableWidth is not updated yet and therefore my buttons are still visible in full screen (or still collapsed when it gets smaller).
scroll_viewer.SizeChanged += (s, e) =>
{
if (!e.WidthChanged) return;
if (scroll_viewer?.ScrollableWidth > 0)
{
scroll_left_button.Visibility = Visibility.Visible;
scroll_right_button.Visibility = Visibility.Visible;
}
else
{
scroll_left_button.Visibility = Visibility.Collapsed;
scroll_right_button.Visibility = Visibility.Collapsed;
}
};
So, after coming back from the weekend with a fresh mind I figured that, in my case, handling the SizeChanged event is the wrong approach since all I actually need to know is if there are any items of my ItemsControl that need scrolling and if the number has changed.
In my question I already checked the ScrollViewer's ScrollableWidth property. It shows how many items are not visible and need scrolling, so it would be enough to check if it has changed and if it's new Value is greater than zero.
ScrollableWidth is a DependencyProperty so I thought about binding to it and listening to the changed event.
So what I did first is creating the new DependencyProperty on my UserControl
// Dependency Property is Private since I only need it internally
private static readonly DependencyProperty scrollable_width_property =
DependencyProperty.Register(nameof(scrollable_width), typeof(double),
typeof(MyUserControl),
new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = false, PropertyChangedCallback = property_changed_callback });
// Wrapper only has get because ScrollableWidth is read only anyway
private double scrollable_width => (double)GetValue(scrollable_width_property);
// Listening to the change
private static void property_changed_callback(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var o = dpo as MyUserControl;
o?.SetButtonVisibility((double)args.NewValue > 0);
}
I removed the scroll_viewer.SizeChanged event and instead created a new public method on my user control to change the button visibility.
public void SetButtonVisibility(bool visible)
{
if (scroll_viewer == null) return;
if (visible)
{
scroll_left_button.Visibility = Visibility.Visible;
scroll_right_button.Visibility = Visibility.Visible;
}
else
{
scroll_left_button.Visibility = Visibility.Collapsed;
scroll_right_button.Visibility = Visibility.Collapsed;
}
}
The last thing that has to be done is the actual binding.
I do it once, in the ItemsControl's Loaded event after obtaining the actual ScrollViewer.
var binding = new Binding(nameof(ScrollViewer.ScrollableWidth))
{
Source = scroll_viewer
};
SetBinding(scrollable_width_property, binding);
Now, whenever the ItemControl needs scrolling (or doesn't), the visibility of the buttons will change regardless of the size change. And now it also works when using the maximize/minimize buttons in the window title.
It's probably not the best implementation since it could probably better use style trigger in xaml instead of the SetButtonVisibility method but it gets the job done.
Edit:
In my case I also had to add a SetButtonVisibility(scroll_viewer.ScrollableWidth > 0); to the ItemsContorol's LoadedEvent because the callback is not triggered at startup.

WPF Copied Button Click not firing

I'm quite new to c# wpf and have a problem.
I have used the answer from this post to duplicate a Grid control. The grid control contains a button. It looks like it is being duplicated correctly.
When the original control's button is pressed, the click event is handled which calls a method in the window's code.
When the copy of the control's button is pressed, the click event is not fired and the method is not called. This is confusing me as I want it to call that same method.
Maybe the event handling data is not being copied properly? Is there a way around this?
Both the origional grid and copied grid (containing the buttons) are children of another grid.
Edit:
This is the xaml for the origional grid which contains a button:
<Grid Name="TempTab" DockPanel.Dock="Left" HorizontalAlignment="Left" Margin="5,5,5,0">
<Rectangle Fill="Black" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke ="White" Margin="0,0,-2,0">
</Rectangle>
<Grid>
<DockPanel LastChildFill="False">
<TextBlock Foreground="White" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="3,0,3,3">Some Text</TextBlock>
<Button Width="50" BorderBrush="{x:Null}" Foreground="{x:Null}" BorderThickness="0" Margin="3,0,0,0" Click="tabdowntest">
<Button.Background>
<ImageBrush ImageSource="TopMenuBar_Close.png" Stretch="Uniform"/>
</Button.Background>
</Button>
</DockPanel>
</Grid>
</Grid>
This grid is a child of a DockPanel with name 'TabsDock'.
It is being copied with the following code:
string gridXaml = XamlWriter.Save(TempTab);
StringReader stringReader = new StringReader(gridXaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
Grid newTab = (Grid)XamlReader.Load(xmlReader);
TabsDock.Children.Add(newTab);
This is the code for the 'Click' event handler which should be called when the either the origional or the copied button's are pressed. But it is only called for the origional:
private void tabdowntest(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("Button Pressed");
}
The bindigs are not set, you need to set them (comment in the orig post):
To be clear, this is only half the solution (as it stood back in 08). This will cause bindings to be evaluated and the results be serialized. If you wish to preserve bindings (as the question asked) you must either add a ExpressionConverter to the Binding type at runtime (see the second part of my question for the relevant link) or see my own answer below for how to do it in 4.0.

ToolBar OverflowPanel remains open

I have a ToolBar with a ItemsTemplate which works fine until the OverflowPanel is Available.
The OverflowPanel does not close if i select one of the context actions.
The Problem only occurs if the Items are added via the ItemsSource binding:
<ToolBar ItemsSource="{Binding ContextActionViewModels}"
Background="Transparent"
ToolBarTray.IsLocked="True"
FocusManager.IsFocusScope="False">
<ToolBar.ItemTemplateSelector>
<views:ContextActionTemplateSelector>
<views:ContextActionTemplateSelector.SimpleContextActionDataTemplate>
<DataTemplate DataType="{x:Type viewModels:SimpleContextActionViewModel}">
<Button Name="Button"
Command="{Binding ActionCommand}"
Style="{StaticResource ToolBarButtonStyle}"
ToolTip="{userInterface:Translation Binding={Binding ToolTip}}">
<ContentControl Template="{Binding Icon,Converter={StaticResource NameToResourceConverter}}"
Margin="5"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Button>
</DataTemplate>
</views:ContextActionTemplateSelector.SimpleContextActionDataTemplate>
<!-- Multiple DataTemplates follow!-->
Why is the DataTemplate / ItemTemplteSelector not working properly. While hardcoded Buttons in XAML work properly?
I uploaded a full sample that illustrates what is not working here:
Just Resize the window and try invoking one off the buttons in the OverflowPanel. While the 'ICommand' is executed properly the Popup stays open.
In the .NET framework source you can find the method that handles the closing behavior of OverflowPanel for ToolBar class:
private static void _OnClick(object e, RoutedEventArgs args)
{
ToolBar toolBar = (ToolBar)e;
ButtonBase bb = args.OriginalSource as ButtonBase;
if (toolBar.IsOverflowOpen && bb != null && bb.Parent == toolBar)
toolBar.Close();
}
When you define a DataTemplate and use ItemsSource property, the Parent property of the created button becomes null and the if check fails. This is the expected behavior of DataTemplate as described here:
For templates, the Parent of the template eventually will be null. To get past this point and extend into the logical tree where the template is actually applied, use TemplatedParent.
As a solution you can set the IsOverflowOpen property to false when you click any of the buttons:
<ToolBar Name="SomeToolBar" ItemsSource="{Binding Items}">
<ToolBar.ItemTemplate>
<DataTemplate DataType="local:ItemViewModel">
<Button Command="{Binding Command}" Content="{Binding Name}" Click="ButtonBase_OnClick"/>
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
SomeToolBar.IsOverflowOpen = false;
}

Sometimes custom loading popup doesn't show

In my application loading popup is shown every time when I call It. But some times It just doesn't show and it is same situation. Popup is a custom UserControl object made with WPF.
Popup is in MainWindow.xaml:
<Viewbox Stretch="Fill">
<Grid>
<Grid x:Name="Body" Height="768" Width="1024" Background="{StaticResource SCBPassiveColor}" />
<src:InfoPane x:Name="InfoPaneMaster" Visibility="Collapsed" VerticalAlignment="Top" HorizontalAlignment="Center" />
</Grid>
</Viewbox>
In the Body of the main window application loads selected layout/view. Before new layout is loaded program calls :
LayoutCommands.DisplayInfoPane(msgLoginSuccess, ROPInfo.Info, null);
implementation:
public void DisplayInfoPane(string infoText, ROPInfo infoType, int? displayTime)
{
StopTimer();
SetTimer(displayTime ?? DefaultDisplayTime);
PrepareInfoPane(infoText, infoType);
colCloseIcon.Width = new GridLength(0);
this.Visibility = Visibility.Visible;
}
The popup automatically closes (sets visibility to collapsed) after default time.
Why sometimes this popup just doesn't show? Is this associated with rendering?

WPF TabItem not highlighted it should

I have three tabs. By simply being clicked individually, they will be highlighted individually as they should.
There are RelyCommand behind these tabs. Whenever the mune is clicked, the program should bring back the first TabItem and it should be highlighted. However, when the second tab is clicked, the first tab would not be highlighted as it should, although it behaves like it does get clicked. It is just not highlighted.
Here is the code behind
xaml code for the two tabs at View level:
<StackPanel Orientation="Horizontal"
Background="{x:Null}">
<TabControl Height="50" Margin="12,0,0,0">
<TabItem Name="tiCaptureSetup" IsSelected="{Binding Path=IsCaptureSetupTabSelected, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<TabItem.Header>
<Button Name="btnCaptureSetup"
Grid.Column="0"
Width="90"
Height="40"
Margin="5"
ToolTip="Capture Setup"
Content="Capture Setup"
Click="btnCaptureSetup_Click"
IsEnabled="{Binding Path=CaptureSetupButtonStatus, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsDefault="True"
></Button>
</TabItem.Header>
</TabItem>
<TabItem Name="tiCapture" IsSelected="{Binding Path=IsCaptureTabSelected, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<TabItem.Header>
<Button Name="btnCapture"
Grid.Column="0"
Margin="5"
Width="90"
Height="40"
ToolTip="Capture"
Content="Capture"
Click="btnCapture_Click"
IsEnabled="{Binding Path=CaptureButtonStatus, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"></Button>
</TabItem.Header>
</TabItem>
The C# code at ViewModel level (CaptureSetup() is the RelyCommand for clicking the first tab, and HardwareSetupLS() is the RelyCommand for the pop-up window on the menu, and RefereshCaptureSetup() is basically trying to retrieve the first tab when the menu window pops up)
public void CaptureSetup()
{
Command command = new Command();
command.Message = "Capture Setup";
command.CommandGUID = new Guid("6ecb028e-754e-4b50-b0ef-df8f344b668e");
_eventAggregator.GetEvent<CommandShowDialogEvent>().Publish(command);
}
public void HardwareSetupLS()
{
//RefereshCaptureSetup(); // refresh panel when hardware setting window is loaded.
Command command = new Command();
command.Message = "HardwareSetupLS";
command.CommandGUID = new Guid("64c695e6-8959-496c-91f7-5a9a95d91e0d");
_eventAggregator.GetEvent<CommandShowDialogEvent>().Publish(command);
RefereshCaptureSetup();
}
public void RefereshCaptureSetup() // refresh CaptureSetup UI
{
_isCaptureSetupTabSelected = true;
_isCaptureTabSelected = false;
_isReviewTabSelected = false;
Command command = new Command();
command.Message = "Capture Setup";
command.CommandGUID = new Guid("{6ecb028e-754e-4b50-b0ef-df8f344b668e}");
_eventAggregator.GetEvent<CommandShowDialogEvent>().Publish(command);
}
I am very confused at this point what else I can do to make the first TabItem highlighted as it should.
I feel like there is some important logic missing in your question (e.g. how the IsCaptureSetupTabSelected and IsCaptureTabSelected are updated) but anyway here are three pointers from looking at your code:
UpdateSourceTrigger=PropertyChanged is useless since your bindings are OneWay (from the source in your ViewModel towards your UI, the source is never updated). If you have written some logic expected to receive IsSelected change notification upon mouse clicks, this won't happen.
You seem to be updating the inner properties wrapped by your bound properties (e.g. _isCaptureSetupTabSelected = true instead of IsCaptureSetupTabSelected = true ) and thus, could be missing the proper INotifyPropertyChanged event that the UI is expecting.
Make sure that the proper TabItem is on focus.

Categories

Resources