Odd Loaded behavior in markup versus code - wpf

I'm currently very confused by the differing behavior regarding the FrameworkElement.Loaded event. I've put together a small example application that demonstrates this.
Xaml:
<Window x:Class="WpfApplication1.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"
Loaded="Window_Loaded">
<Grid>
<TabControl>
<TabItem Header="Tab 1" />
<TabItem Header="Tab 2" >
<WindowsFormsHost Name="formHost" Loaded="formHost_Loaded" />
</TabItem>
</TabControl>
</Grid>
</Window>
Code:
using System.Windows;
namespace WpfApplication1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
formHost.Loaded += delegate
{
MessageBox.Show("Delegate");
};
}
private void formHost_Loaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("Markup");
}
}
}
As it is, when I run the application I get two immediate MessageBoxes - "Markup" and "Delegate". If, however, I remove Loaded="formHost_Loaded" from the WindowsFormsHost, I get neither on startup. It obviously makes sense why I don't get the "Markup" dialog, but why does this also remove the "Delegate"? I would imagine it has to do with the order in which the events are called (Window versus its children), but I'm having a tough time figuring it out.
Note: You can replace the WindowsFormsHost with other controls, it really shouldn't matter - I was just using it for another few tests.

The Loaded event handlers are determined before any of them are called, as the Loaded event is effectively broadcast all at once starting at the top of the tree. Since you added a handler after WPF has decided what handlers need to be called, it is ignored.
This can be verified using Reflector. Specifically, the BroadcastEventHelper.BroadcastLoadedSynchronously method will call the BroadcastEventHelper.BroadcastEvent method with the LoadedEvent routed event.
BroadcastEvent method gathers all the objects in the visual tree that have a Loaded event handler first, then loops through and raises the events on those objects.

Related

How can I showdialog on the mainwindow when it is inactive?

I am developing an app with WPF.
In my case, the users have 2 monitors. When he opens the app in the secondary screen, it will load data for nearly 5 seconds. During this period, he may turn to the primary screen for personal stuff, like visit Facebook or visit twitter.
After the data are loaded, a dialogbox should be prompted. What bothers me is that it often shows in the primary screen where he deals with personal stuff, not the secondary screen where he opens the app. The dialog window is supposed to show on the top of the app.
I am thinking it's because that the app is not active when the data are loaded. Do you guys have any idea?
I know that the MessageBox.Show() has a "owner" parameter can fix this. How can I automatically get the correct owner? I am using a PRISM pattern so that it's not easy for me to find the window as the owner.
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
await Task.Delay(5000);
MessageBox.Show("Loaded!");
}
}
Yes the owner parameter should fix that. Regarding Prism, there are different approaches. The easiest for me was to use the Application.Current.MainWindow which refers to the main window of the application.
MessageBox.Show(Application.Current.MainWindow, "bla bla");
You could as well try to resolve your Shell window via your IoC container and take this as owner.
You could try to use MessageBox from Extended WPF Toolkit Community Edition (NuGet Package Extended.Wpf.Toolkit):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="350" Width="525">
<Grid>
<xctk:WindowContainer>
<xctk:ChildWindow Height="100" Width="250" Left="10" Top="10" Name="chWindow">
<TextBlock Text="Hello World ..." />
</xctk:ChildWindow>
<xctk:MessageBox Height="100" Width="250" Left="10" Top="100" Name="msgBox">
</xctk:MessageBox>
</xctk:WindowContainer>
<Button Name="btnTst" Click="btnTst_Click" Content="Test" Width="65" Height="30" HorizontalAlignment="Left" VerticalAlignment="Bottom" />
</Grid>
</Window>
And:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btnTst_Click(object sender, RoutedEventArgs e)
{
await Task.Delay(5000);
//this.chWindow.Show();
this.msgBox.ShowMessageBox();
}
}
MessageBox is inside Window…

Why is RoutedEventArgs.Handled ignored between TouchDown and Click

I am trying to support both TouchDown and Click with the same event.
You will notice that the following code, instead of toggling between green and red, just flashes red and goes back to green. As far as I can tell, this is because the click event is ignoring the Handled property of the RoutedEvent. I need this to work with both Touch and Mouse.
XAML:
<Window x:Class="CodeSpace.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Background="LightGreen">
<Grid>
<Button Content="Touch Me" HorizontalAlignment="Center" VerticalAlignment="Center" Height="75" Width="75" Click="OnClick" TouchDown="OnClick"/>
</Grid>
</Window>
Code behind:
using System.Windows;
using System.Windows.Media;
namespace CodeSpace.WPF
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnClick(object sender, RoutedEventArgs e)
{
Background = (Background.Equals(Brushes.LightGreen)) ? Brushes.LightCoral : Brushes.LightGreen;
e.Handled = true;
}
}
}
Note: the reason I am not just using Click alone (which is supposed to work with Touch) is because with my specific case when I do that, the first time the button is touched nothing happens. All subsequent touches work just fine. This is a separate issue that I can't ask about because I cannot reproduce it in a simple code example. Also, this behavior only happens when windows is set to 120 DPI. Everything works just fine in 96 DPI. I have no idea!
The click event comes as a result of MouseUp/TouchUp rather than *Down (unless you change the ClickBehavior of the button). This answer has a good approach to globally blocking the promotion of mouse events to touch events for an element: https://stackoverflow.com/a/19540120/518955

How to build a generic/re-usable modal dialog for WPF following MVVM

I would like to build a generic/re-usable modal dialog that I can use in our WPF (MVVM) - WCF LOB application.
I have a Views and associated ViewModels that I would like to display using dialogs. Bindings between Views and ViewModels are done using Type-targeted DataTemplates.
Here are some requirements that I have been able to draft:
I prefer this to be based on a Window instead of using Adorners and controls that act like a modal dialog.
It should get its minimum size from the content.
It should center on the owner window.
The window must not show the Minimize and Maximize buttons.
It should get its title from the content.
What is the best way to do this?
I usually deal with this by injecting this interface into the appropriate ViewModels:
public interface IWindow
{
void Close();
IWindow CreateChild(object viewModel);
void Show();
bool? ShowDialog();
}
This allows the ViewModels to spaw child windows and show them modally on modeless.
A reusable implementation of IWindow is this:
public class WindowAdapter : IWindow
{
private readonly Window wpfWindow;
public WindowAdapter(Window wpfWindow)
{
if (wpfWindow == null)
{
throw new ArgumentNullException("window");
}
this.wpfWindow = wpfWindow;
}
#region IWindow Members
public virtual void Close()
{
this.wpfWindow.Close();
}
public virtual IWindow CreateChild(object viewModel)
{
var cw = new ContentWindow();
cw.Owner = this.wpfWindow;
cw.DataContext = viewModel;
WindowAdapter.ConfigureBehavior(cw);
return new WindowAdapter(cw);
}
public virtual void Show()
{
this.wpfWindow.Show();
}
public virtual bool? ShowDialog()
{
return this.wpfWindow.ShowDialog();
}
#endregion
protected Window WpfWindow
{
get { return this.wpfWindow; }
}
private static void ConfigureBehavior(ContentWindow cw)
{
cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
}
}
You can use this Window as a reusable host window. There's no code-behind:
<Window x:Class="Ploeh.Samples.ProductManagement.WpfClient.ContentWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:Ploeh.Samples.ProductManagement.WpfClient"
xmlns:pm="clr-namespace:Ploeh.Samples.ProductManagement.PresentationLogic.Wpf;assembly=Ploeh.Samples.ProductManagement.PresentationLogic.Wpf"
Title="{Binding Path=Title}"
Height="300"
Width="300"
MinHeight="300"
MinWidth="300" >
<Window.Resources>
<DataTemplate DataType="{x:Type pm:ProductEditorViewModel}">
<self:ProductEditorControl />
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding}" />
</Window>
You can read more about this (as well as download the full code sample) in my book.
I'm answering my own question to help others find all answers I struggled to find in one place. What above seems like a straight forward problem, actually presents multiple problems that I hope to answer sufficiently below.
Here goes.
Your WPF window that will serve as the generic dialog can look something like this:
<Window x:Class="Example.ModalDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ex="clr-namespace:Example"
Title="{Binding Path=mDialogWindowTitle}"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
WindowStyle="SingleBorderWindow"
SizeToContent="WidthAndHeight"
ex:WindowCustomizer.CanMaximize="False"
ex:WindowCustomizer.CanMinimize="False"
>
<DockPanel Margin="3">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Content="Cancel" IsCancel="True" Margin="3"/>
<Button Content="OK" IsDefault="True" Margin="3" Click="Button_Click" />
</StackPanel>
<ContentPresenter Name="WindowContent" Content="{Binding}"/>
</DockPanel>
</Window>
Following MVVM, the right way to show a dialog is through a mediator. To use a mediator, you typically require some service locator as well. For mediator specific details, look here.
The solution I settled on involved implementing an IDialogService interface that is resolved through a simple static ServiceLocator. This excellent codeproject article has the details on that. Take note of this message in the article forum. This solution also solves the problem of discovering the owner window via the ViewModel instance.
Using this interface, you can call IDialogService.ShowDialog(ownerViewModel, dialogViewModel). For now, I'm calling this from the owner ViewModel, meaning I have hard references between my ViewModels. If you use aggregated events, you will probably call this from a conductor.
Setting the minimum size on the View that will eventually be displayed in the dialog doesn't automatically set the minimum size of the dialog. Also, since the logical tree in the dialog contains the ViewModel, you can't just bind to the WindowContent element's properties. This question has an answer with my solution.
The answer I mention above also includes code that centers the window on the owner.
Finally, disabling the minimize and maximize buttons is something WPF can't natively do. The most elegant solution IMHO is using this.

Create a custom click event handler for a WPF usercontrol which contains a button?

have you ever found a problem when assigning a click event handler for your custom WPF usercontrol with a nested button control? I do.
When you put such user control in a main window, let's say Main.xaml, the MouseLeftButtonDown doesn't work, but the PreviewMouseLeftButtonDown works like a charm.
But imagine yourself telling each developer in your team to use this event when using your usercontrol... Some usercontrols in you library has MouseLeftButtonDown, others PreviewMouseLeftButtonDown.... It's a mess don't you agree?
So I've got a solution but I want someone to see if there's some elegant way to create your custom event handler called "Click".
In my usercontrol called CustomButton.xaml.cs, I have so far:
public partial class CustomButton: UserControl
{
public CustomButton()
: base()
{
this.InitializeComponent();
}
public delegate void ClickHandler(object sender, EventArgs e);
public event EventHandler Click;
public void Button_Click(object sender, RoutedEventArgs e) {//execute daddy's button click
(((sender as Button).Parent as Grid).Parent as CustomButton).Click(sender, e);
e.Handled = false;
}
In my CustomButton.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="YourCompany.UI.Controls.CustomButton" d:DesignHeight="72.5" d:DesignWidth="200">
<UserControl.Resources>
blablabla
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Button Style="{DynamicResource CustomButton}"
Width="{Binding ElementName=CustomButton, Path=ActualWidth}"
Cursor="Hand" Foreground="#ffffff" FontSize="28" Margin="8,8,0,12"
HorizontalAlignment="Left"
Content="Custom Button" Click="Button_Click" />
</Grid>
Now in my Main.xaml, the caller, I have:
<Window x:Class="YourCompany.MyProject.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyProject!" Height="600" Width="800"
MinWidth="800" MinHeight="600" WindowState="Maximized" WindowStartupLocation="CenterScreen"
xmlns:bigbola="clr-namespace:YourCompany.UI.Controls;assembly=YourCompany.UI.Controls">
<mycontrols:CustomButton Name="test" MyImage=".\Images\btnOptions.png" Cursor="Hand"
Texto="See options" Click="test_Click"
Margin="168.367,176.702,253.609,0" ToolTip="See all options" Height="76.682"
VerticalAlignment="Top"></mycontrols:CustomButton>
Explanation:
in the usercontrol, when you click the nested button, it executes its parent custom "Click" handler.
Is there a elegant way to accomplish the same effect?
Going off of what mdm20 was saying... Why are you creating a UserControl (a collection of controls grouped into 1) when you could much more easily create a CustomControl (a control that extends the functionality of an existing control, such as a Button)? Assuming a Button is the only control you'd like in CustomButton, I'd highly recommend a CustomControl over what you have (a UserControl).
Example of UserControl vs CustomControl here
Hope this helps!
If your implementing a button, why not just derive from button?
To answer your question though, all you need it this.
if (Click != null) Click(this, EventArgs.Empty);
Couldn't this line:
(((sender as Button).Parent as Grid).Parent as CustomButton).Click(sender, e);
be replaced by
this.Click(sender, e);
?
Other than that though the answer depends on the exact behaviour that you want. If you want to click event of your user control to only trigger when you click on the inner button then I think you are handling it the right way. On the other hand if you want the click event to trigger whenever you click anywhere within the bounds of the user control then you are probably best styling or inheriting from the standard button control. Remember that in WPF the button's content can be any other element including another button.

Error when binding a Dependency Property on a custom Behavior

I am exploring the Silverlight attached behaviors mechanism in order to use the Model-View-ViewModel pattern within my Silverlight applications. To start with, I am trying to get a simple Hello World working, but I am completely stuck in an error for which I'm not able to find a solution.
What I have right now is a page that just contains a button which should display a message when clicked. The click event is handled by using a class derived from Behavior, and the message is specified as a dependency property of the behavior itself. The problem comes when trying to bind the message property to a property on a viewmodel class used as the data context: I get an exeption in the call to InitializeComponent in the view.
Here is all the code I'm using, as you can see it is rather simple. First the markup of the main page and the view it contains:
MyPage
<UserControl x:Class="MyExample.MyPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyExample"
>
<Grid x:Name="LayoutRoot">
<local:MyView/>
</Grid>
</UserControl>
MyView (the TextBlock is there just to check that the binding syntax is correct)
<UserControl x:Class="MyExample.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyExample"
Width="400" Height="300">
<StackPanel Orientation="Vertical" x:Name="LayoutRoot" Background="White">
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
</StackPanel.Resources>
<TextBlock Text="This button will display the following message:"/>
<TextBlock Text="{Binding MyMessage, Source={StaticResource MyResource}}" FontStyle="Italic"/>
<Button x:Name="MyButton" Content="Click me!">
<i:Interaction.Behaviors>
<local:MyBehavior Message="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</i:Interaction.Behaviors>
</Button>
</StackPanel>
</UserControl>
Now the code, there are two classes: one for the behavior and another one for the viewmodel:
MyViewmodel
public class MyViewmodel
{
public string MyMessage
{
get { return "Hello, world!"; }
}
}
MyBehavior
public class MyBehavior : Behavior<Button>
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(MyBehavior),
new PropertyMetadata("(no message)"));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Click -= new RoutedEventHandler(AssociatedObject_Click);
}
void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Message);
}
}
Simple enough, but this code throws an AG_E_PARSER_BAD_PROPERTY_VALUE [Line: 15 Position: 43] (right at the start of the value being set for the Message property) exception when ran. I'm sure that I'm missing something, but what?
Additional information: if I remove the binding from the Message property on MyBehavior (that is, if I set its value to any static string), it works fine. Also, I'm targeting silverlight 3 RTW.
Thanks a lot!
UPDATE
It seems that unlike WPF, Silverlight does not support data binding on any object deriving from DependencyObject, but only on objects deriving from FrameworkElement. This is no the case for Behavior, hence binding does not work.
I have found a workaround here, in the form of something named surrogate binders. Basically you specify the element and property to be binded, as well as the value, as attributes of the FrameworkElement containing the non-FrameworkElement object.
UPDATE 2
The surrogate binder does not work when the FrameworkElement contains an Interaction.Behaviors sub-element.
I have found another solution here, and this one seems to work. This time, the trick used is a DeepSetter. You define one of such setters as a static resource on the containing StackPanel, and then reference the resource from the behavior. So in my example, we should expand the StackPanel resources section as follows:
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
<local:DeepSetter
x:Key="MyBehaviorSetter"
TargetProperty="Message"
BindingExport="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</StackPanel.Resources>
...and modify the button's behavior declaration as follows:
<local:MyBehavior local:DeepSetter.BindingImport="{StaticResource MyBehaviorSetter}"/>
UPDATE 3
Good news: data binding for any DependecyObject will be available on Silverlight 4: http://timheuer.com/blog/archive/2009/11/18/whats-new-in-silverlight-4-complete-guide-new-features.aspx#dobind
To get the DataBinding support the class should inherit from FrameworkElement.Hoping MSFT will give support in Silverlight 4

Resources