Popup vs. Visibility toggle - wpf

I have a couple of views that I display as overlays on the main window by binding the visibility to a boolean property in my ViewModel.
The main window looks something like this:
<Grid>
<vw:MainContentView/>
<!-- Overlays (normally invisible) -->
<vw:NotificationsView/>
</Grid>
The 'NotificationsView' has a style that looks like this:
<Style x:Key="NotificationsView" TargetType="UserControl">
<!-- snipped -->
<Style.Triggers>
<DataTrigger Binding="{Binding IsNotificationsViewVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsNotificationsViewVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
I am wondering whether it might be preferable to use a Popup with the 'IsOpen' property bound to the 'IsNotificationViewVisible' property in the ViewModel, rather than toggling the visibility of the view directly:
<Grid>
<vw:MainContentView />
<!-- Popups (normally invisible) -->
<Popup IsOpen="{Binding IsNotificationsViewVisible}">
<vw:NotificationItemsView/>
</Popup>
</Grid>
Are there any reasons I'd want to use one approach over the other (memory usage or otherwise)? Both approaches seem to work just fine - the Popup comes with a couple of free animations but otherwise looks the same.

If you're displaying content on an already existing window, you should probably use the Visibility approach. The Popup acts as a mini-window that can initially position itself based on the location of elements in the main window. That means that it can go outside the bounds of the main window and won't move around when you move the main window. It also doesn't effect the layout of other elements in the window. I imagine it also has some overhead associated with this, but I have not run any exact numbers.
As a bonus, I'll throw in a suggestion: you could use a converter to make Visibility binding easier.

Related

Adding an ErrorTemplate to a WPF User Control disables TextBox input

I have a very basic custom control consisting of a Label and a Textbox. I've used my control for sometime without any issues.
I've now come to styling my application and have my style inside a XAML file containing just a ResourceDictionary. I have the following for my UserControl:
<Style TargetType="local:LabelEdit">
<Setter Property="Background" Value="{StaticResource BackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder />
<Image Source="/AJSoft.Controls;component/Resources/Icons/cross.ico" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Foreground" Value="{StaticResource ErrorForegroundBrush}" />
<Setter Property="Background" Value="{StaticResource ErrorBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource ErrorBorderBrush}" />
</Trigger>
</Style.Triggers>
</Style>
Everything works absolutely fine if I comment out the Setter for Validation.ErrorTemplate. If the ErrorTemplate is left intact, the cross shows (I haven't sorted out placement yet, but that can come later...), but the Textbox component of my UserControl does not show the caret or accept keyboard input. The rest of the controls in my Window work as expected.
Here are some screenies where I've deliberately put in some erroneous text to show how it looks.
The same problem happens even if I change that huge image to be a textblock with a small red "!" - the image is just for effect for now.
What am I doing that's causing the problem? I'm new to Validation in WPF...
EDIT: The image shown (big red cross) is just one example of what I've done. Even if I use a small image shown alongside the UserControl, I still get the same effect.
If you were to look at how error templates usually work, you'd see they apply to a single control.
Part of the issue you have here is you've got a label and textbox in one parent user control.
If you then apply an error template at the usercontrol level, it's on everything in that. Label, textbox, everything in your usercontrol.
The next thing to consider is how your error template ends up visible on top of everything. This happens because your error template generates UI in the adorner layer. That's top of everything ( in the window ).
Add these together and you got a big image on top of the content of your usercontrol.
At risk of over simplifying:
You put a top on your box and you can't now get at what's in that box.
There are several ways you could "fix" this but they all involve some design change or compromise.
Maybe a big X on top of your input control isn't a good idea.
You could kind of make it work.
You could make your image IsHitTestVisible="False".
It'll be visually in the way but you can then likely click on the textbox and type.
Just maybe not see everything.
Probably not ideal.
You could show your cross next to the textbox using a datatrigger rather than error template.
Add an image to your usercontrol so you have label, textbox, CrossImage.
Add a style to that with a setter makes it visible collapsed by default.
Use a trigger in that style to show the CrossImage when the control has errors.
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter ... />
</Trigger>
</Style.Triggers>
You may well find it simplest to use the tag on the usercontrol and set that to visible/collapsed. Bind visibility of the image to that.

IsMouseOver trigger for DockPanel not firing unless children are hovered over

This question has been asked a couple of times before, but at this time, none of the answers I've found turned out to be working.
So I have a custom DockPanel with 2 child elements inside, 1 TextBox and 1 Image.
I intended to have the DockPanel change color on hover, so I put a Trigger inside of it. But currently, it only changes color if I hover over the children, whereas a hover over whitespace between elements doesn't do anything.
I've read similar posts which mentioned using isHitTestVisible, but it doesn't do anything.
Short version of my code is the following:
<DockPanel>
<!-- Change color on mouse hover -->
<DockPanel.Style>
<Style>
<Style.Triggers>
<Trigger Property="DockPanel.IsMouseOver" Value="True">
<Setter Property="DockPanel.Background" Value="#FFe6ffe6"/>
</Trigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
<StackPanel Orientation="Vertical" DockPanel.Dock="Center">
<!-- TextBox and Image code here -->
</StackPanel>
</DockPanel>
Add this to your dock panel style.
<Trigger Property="DockPanel.IsMouseOver" Value="False">
<Setter Property="DockPanel.Background" Value="Transparent"/>
</Trigger>

WPF Xaml control converted to an image has incorrect layout

I have a control that I am converting to an image using the method here :
Force rendering of a WPF control in memory
Unfortunately I have a complex layout and it seems that a control being 'Collapsed' isn't actually being hidden properly in the output image.
Tried:
Call UpdateLayout multiple times
Change size of control by 1 pixel
Using a ViewBox
It seems to affect DockPanel if something is aligned to the bottom and hidden with a converter.
<DockPanel LastChildFill=True>
<Something Dock.Panel="Top" />
<Something Dock.Panel="Bottom" Binding="{Binding XXXXX, Converter={StaticResource booleanConverter}}"/>
<Something Dock.Panel="Bottom" Binding="{Binding YYYYY, Converter={StaticResource booleanConverter}}"/>
<Something />
</DockPanel>
Everything displays just fine in the Xaml editor, or if it is used at runtime in a real visible control.
In the end I had to use a trigger to set the height to zero instead of applying Collapsed to the elements. Of course this means any margins must be converted to padding, with nested panels if needed.
In this instance I had a border control - so I had to remove the Visibility property and use this trigger instead.
<Border>
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding XXXXX, Converter={StaticResource booleanConverter}}" Value="true">
<Setter Property="Border.Height" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>

WPF MVVM Light - About views communicating

I am currently working on a project for work. I am seeking an outside design opinion, as well as some general information on the issue I am faced with.
We have a MainWindow.xaml file that is located in the root directory of the project. In this main window is some design and logic for some collapsing stack panels, ribbon toolbar, etc.
So far the idea is to include a different in each stack panel to help make the code neat. The views are located in a 'Views' folder. So just to be clear, the MainWindow.xaml and other views ARE NOT in the same directory. This is open to change, if necessary.
So here is my question/issue: We have a Window ('A'), a main panel with a collapsable stack panel with some information ('B') that is contained in window 'A'. Then there is another stack panel to manage the contents in 'B', (collapse/visisble) ('C').
'A' contains a toggle button to show/collapse 'B'.
'B' contains a button to show/collapse 'C'.
'C' contains a button to show/collapse itself, 'C'.
'C' should have its logic all contain within a view, so the MainWindow ('A') should have a simple tag:
<StackPanel Style="{StaticResource FrameGradient}" Tag="{Binding ElementName=ToggleButton}">
<view:Content></view:Content>
</StackPanel>
Currently, the bindings for toggling the buttons within 'A' are in the styling. The In this case FrameGradient has triggers like so:
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
//Setter properties
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
Is it possible to, within the 'Content' View to TOGGLE the panel, 'C', which is NOT within the view? I feel like I am missing a core idea of XAML here. I found a 'cheap' work around which is to place the 'Close' button from the Content View outside of the tags, but then that leads to styling issues and I feel like I shouldn't have to do something silly like that. Again, the idea is that the toggle button for Stack Panel 'C' is contained within another view and I want to be able to toggle it from another view.
I apologize if I am not clear enough, I will provide whoever asks with more information if required here.
UPDATE
I have some time to actually add the code I am using so that this might make more sense.
MainWindow.xaml - Logic for Filter panel (Located in root)
<StackPanel Grid.Row="1" Grid.Column="4" Visibility="Collapsed" Style="{StaticResource FrameGradient}">
<Grid x:Name="FilterContentGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<view:Filters></view:Filters>
</Grid>
</StackPanel>
Filters.xaml - Logic for Filters view (Located in /Views)
The button within the file that needs to Collapse the above StackPanel.
<Button x:Name="FilterManagementCloseButton" Content="CLOSE"></Button>
Theme.Xaml - Logic for all styling (Located in root, along with MainWindow.xaml and App.xaml)
Button Styling
<Style x:Key="FilterManagementCloseButton" TargetType="Button">
<Setter Property="Padding" Value="10,5,20,3" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource AncestorType={x:Type Local:MainWindow}}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
And finally, the FrameGradient Styling also located in Theme.xaml
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
SO, I hope this makes things more clear. I want the CLOSE button within Filters.xaml to COLLAPSE the stackpanel that is located in MainWindow. I realize this code is a mess at the moment.
Is it possible to, within the 'Content' View to TOGGLE the panel, 'C',
which is NOT within the view?
Create a shared VM which each other VM will have a property for which it can access. This VM can be loaded with during initialization of the other VMs. To allow for changes to happen put INotifyProperty(ies) on the shared VM which will then flag the desired logic across all views. Finally bind the target control(s) to your datacontext as normal except sub path into the shared VM target's property.
Hence when one view toggles (two way binding) a shared property it is reflected on the view of the target panel.
Update Example
The idea here is that one creates a viewmodel for the AppPage. That VM will hold generic flags which are shared across all viewmodels. Each subsequently created ViewModel will have a reference to the AppPage's viewmodel.
The example below is a mainpage where the AppVM contains a flag which informs the mainpage whether a login is in process. If it is and that value is true then a bound button on the mainpage will be enabled.
Subsequently the mainpage can override the appvm and put a new value within that flag by a bounded checkbox that can in-directly change whether the button is enabled; thus changing the flag for all other VMs in the process.
Here is the Mainpage VM, for this example I simply create the AppVM, but it could be passed in, or gotten from a static reference elsewhere. NOTE also how I don't care when AVs (appVM) property changes; it is not required for this example (we are not binding anything to AppVM, just its properties which need to be monitored).
public class MainVM : INotifyPropertyChanged
{
public AppVM AV { get; set; }
public MainVM()
{
AV = new AppVM() { LoginInProcess = true };
}
}
Here is the AppVm
public class AppVM : INotifyPropertyChanged
{
private bool _LoginInProcess;
public bool LoginInProcess
{
get { return _LoginInProcess; }
set { _LoginInProcess = value; OnPropertyChanged(); }
}
}
Here is MainPage's Xaml where the datacontext has been set to an instance of MainVM:
<StackPanel Orientation="Vertical">
<CheckBox Content="Override"
IsChecked="{Binding AV.LoginInProcess, Mode=TwoWay}"/>
<Button Content="Login"
IsEnabled="{Binding AV.LoginInProcess}"
Width="75" />
</StackPanel>
I base the MVVM off of my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding which explains the other missing items of this example such as the mainpage's datacontext loading.
You can use RelativeSource Bindings to bind from child views to properties in parent view models. Let's say you have a ToggleButton in MainWindow.xaml that is data bound to a property named IsChecked, which is declared in the object that is data bound to the MainWindow DataContext property. You could data bind to that same property from any child view with a RelativeSource Binding, like this:
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
<Setter Property="StackPanel.Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.IsChecked, RelativeSource={
RelativeSource AncestorType={x:Type Local:MainWindow}}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<!-- Note that there is no need for two Triggers here -->
<!-- One Setter and one Trigger is enough -->
</Style.Triggers>
</Style>

ContentControl change ContentTemplate on GotFocus

I have a UserControl which contains a ContentControl. When the user clicks this ContentControl I want to change its ContentTemplate, to make it "editable" (instead of labels display textboxes for example).
What I have is this:
<StackPanel>
<ContentControl Style="{DynamicResource ContainerStyleEditable}" GotFocus="ContentControl_GotFocus"></ContentControl>
</StackPanel>
and in userControl resources i have
<Style TargetType="{x:Type ContentControl}" x:Key="ContainerStyleEditable">
<Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplateReadOnly}" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplateEditable}" />
</Trigger>
</Style.Triggers>
</Style>
This doe not work, it seems the GotFocus event never fires. What is the way to to this?
I usually base my triggers of IsKeyboardFocusWithin instead of IsFocused because often the focused element usually isn't the actual ContentControl, but rather a control inside it's Content.
Also, be sure that at least one control inside the ContentControl can accept focus so the control can get focus. If nothing inside the control can accept focus, your trigger will never fire.

Resources