The event IsVisibleChanged couldn't be routed to the ViewModel. What could be the cause?
If I'm testing the event as normal WPF event (no Caliburn Message.Atach) with CodeBehind, the Event is fired as expected. If I'm testing the Caliburn Message.Atach with other events of the UserControl like LayoutUpdated, they work like expected with the ViewModel. But I'm not able to get IsVisibleChanged fired to my ViewModel.
View
<UserControl x:Class="MySetupDeviceConfig.Views.SetupDeviceConfigView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cal="http://www.caliburnproject.org"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Visibility="{Binding Visibility}"
d:DesignHeight="450" d:DesignWidth="800"
cal:Message.Attach="[Event IsVisibleChanged] = [Action UcIsVisibleChanged];">
<Grid>
...
ViewModel
public class SetupDeviceConfigViewModel : Screen
{
private Visibility _Visibility;
private ILogger Log { get; set; }
public Visibility Visibility { get => _Visibility; set { _Visibility = value; NotifyOfPropertyChange(); } }
// ...
public void UcIsVisibleChanged()
{
Log.LogInformation("IsVisibleChanged");
}
Tested with Caliburn.Micro v4.0.62-alpha and CaliburnMicro v3.2.0
Changing e.g. to the Loaded event in the view with same action/function mapping -> it works. So there is no type mismatch...
cal:Message.Attach="[Event Loaded] = [Action UcIsVisibleChanged];">
The reason for this not working is that IsVisibleChanged is a CLR event and not a routed event. As stated in the documentation.
Caliburn.Micro's Message system works on routed event not CLR events. Since Caliburn.Micro uses EventTrigger internally.
Why not just make a binding to IsVisible?
Doesn't your property need to be called IsVisible rather than Visibility? Or, change the call to NotifyOfPropertyChange from the default to NotifyOfPropertyChange("IsVisible").
Try placing the action on the first element (the Grid) inside your UserControl rather than on the UserControl itself. I tried it myself and it seemed to work on the Grid, but not on the UserControl itself, could be a Caliburn bug?
I noticed also that when I toggled the Visibility of the Grid in code-behind, the event didn't get fired, but when I bound the Visibility dependency property to a property in my ViewModel, it worked! Seems like another bug in Caliburn.
I think it's usually good practice to place events and bindings on controls inside the UserControl rather than the UserControl itself. If the UserControl gets hidden from the outside, the Grid inside it shoud fire the visibility event anyway, so it practically makes no difference.
Related
I have a WPF application, which uses User Settings, and binds it to a <TextBox> like this:
<Window x:Class="SampleApp.MainWindow"
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">
<Grid>
<TextBox x:Name="textbox" Text="{Binding Source={StaticResource Settings}, Path=Default.Folder}"/>
</Grid>
</Window>
The App.xaml looks like this:
<Application x:Class="SampleApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:SampleApp.Properties"
ShutdownMode="OnExplicitShutdown"
Startup="Application_Startup">
<Application.Resources>
<properties:Settings x:Key="Settings"/>
</Application.Resources>
</Application>
And here's the App.xaml.cs:
public partial class App : Application
{
public App()
{
}
private void Application_Startup(object sender, StartupEventArgs e)
{
new MainWindow().ShowDialog();
}
}
This works really great one way: My TextBox always displays the content of MySetting when the window is shown.
The other way around doesn't quite work as I intend. What does work, is when the user manually writes into the TextBox.
What doesn't work, is when I programmatically make changes to the TextBox, like this:
textbox.Text = folderBrowserDialog.SelectedPath;
In this case, MySetting doesn't update until the user types into the TextBox.
My current solution is to do this:
Properties.Settings.Default.MySetting = textbox.Text;
But it defeats the point of having a two-way data binding.
What should I do to have data binding work both ways, even when I'm programmatically changing the user control?
But it defeats the point of having a two-way data binding.
No, not really.
The point of two-way binding is so that when the code modifies the source property the UI's target property is updated, and when the user modifies the target property, the source property is updated.
Two-way binding is definitely not there so that you can assign a value programmatically to the target property and have it reflected in a source property that you could have and should been setting instead.
It should be very rare for someone writing WPF code to ever have to name or interact with a UI element defined in XAML. And when that does happen, it should be only to implement user interface features, such as drag-select, drag & drop, key handling, etc.
Putting it another way: in the MVVM paradigm, the view model data structure is the only thing that non-UI code ought to be dealing with. The binding mechanism provides the mediator between the business logic, represented by the view model (and optionally, a model behind that), and the user interface, represented by the XAML.
Indeed, typically if you were to set the target property explicitly, it would discard the binding, causing it to not work at all.
So, the right way to do this is, in the code-behind, to only ever interact with the MySetting property. If you want to update the value shown to the user, then you need to change the code-behind property that is bound to that value.
I'm currently working on a WPF project using Caliburn.Micro and want to bind a KeyDown event to a UserControl. It should be fired if the Window is opened and the user pushes any button.
<UserControl x:Class="Test.Views.AppView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="http://www.caliburnproject.org"
cal:Message.Attach="[Event KeyDown] = [TestMethod($executionContext)]" />
Unfortunately this code doesn't work. Is it even possible to bind an Event to the UserControl and not to specific controls like TextBox or Button?
If a control on this UserControl has focus it will receive the KeyDown event first and handle it. This will prevent the UserControl from receiving it.
Use PreviewKeyDown to capture the event. The Preview... events are exactly for this kind of scenario. They bubble up from the root to the child controls whereas the regular events tunnel down.
Don't forget to set e.Handled = true; at the end of a handler if the bubbling and tunneling should stop.
I have a UserControl who's DataContext is being set to an instance of a ViewModel (using MVVM). But, I have controls within the UserControl which need to be bound to properties that only pertain to the view (which is why I placed them in code behind). I'm not sure how to bind this in xaml appropriately:
Note: SelectedOrderType is a property on the View-Model, and OrderTypes is a property on the UserControl itself.
<UserControl x:Class="MyNamespace.OrderControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="OrderUserControl">
<Grid>
...
<ComboBox ItemsSource="{Binding Path=OrderTypes, ElementName=OrderUserControl}"
SelectedValue="{Binding Path=SelectedOrderType}"
SelectedValuePath="OrderTypeCode"
DisplayMemberPath="OrderTypeName" />
</Grid>
</UserControl>
public partial class OrderControl : UserControl
{
public OrderControl()
{
InitializeComponent();
OrderTypes = ...;
}
public IReadOnlyCollection<OrderTypeInfo> OrderTypes { get; private set; }
}
Also, I know I can simply create a property on the View-Model, and I get that some people would suggest that that would be the correct place to put it... but I really would like to know how I could do what I'm attempting to do if not for this scenario, maybe for other scenarios in the future?
I may be wrong but would you not need to make a dependency property on your user control for "SelectedOrderType" and bind the the View Model to that property not bind directly to the view model from the user control.
That way your UserControl is not dependent on the view model?
Edit:
I think you could set it up the way you have it, but the binding for SelectedOrderType would need to be something like {Binding Path=DataContext.SelectedOrderType, ElementName=OrderUserControl}
I have a Window with a ContentControl inside. I want to show multiple view-filling UserControls like a wizard with multiple steps. Those UserControls need their own ViewModel and the possibility to replace themselves with another UserControl in the Window's ContentControl.
I want to work with the MVVM pattern and am currently struggling how to access the Window's ViewModel from the ViewModel of the UserControl.
Here is the simplified code I have so far. The content changing works without any problem when I change it inside the main ViewModel:
Window XAML:
<Grid>
<ContentControl Content="{Binding CurrentView}" />
</Grid>
Window ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private object currentView;
public object CurrentView
{
get { return currentView; }
private set
{
currentView = value;
OnPropertyChanged(); // <- Property name is set automatically, so no parameter needed
}
}
public MainWindowViewModel()
{
this.CurrentView = new UserControl1(); // Initial view to show within the ContentControl
}
}
UserControl1 XAML:
<UserControl>
<Grid>
<Button Command="{Binding SwitchToUserControl2}">Switch content</Button>
</Grid>
</UserControl>
Now I have the following "thinking problems":
If I set the DataContext of the UserControl to its ViewModel, I cannot access the MainWindowViewModel to change the CurrentView Property to UserControl2.
If I don't set the DataContext of the UserControl, I automatically inherit the correct ViewModel for Binding the Command to change the Content but haven't instantiated the ViewModel of the UserControl. I need this because many actions of the UserControl should be handled within it's own ViewModel.
In my understanding it is neccessary to have access to both ViewModels from the view but have no clue how to achieve this.
I would not have the MainWindowViewModel create a view, but rather create your first ViewModel. The ViewModel could then use events or any other mechanism to notify that it should transition to the next step.
The View portion can be handled easily in that case via DataTemplates that map the ViewModel to the appropriate View. The advantage here is that the ViewModel never knows about the View used to render it, which stays "pure" in an MVVM perspective. Right now, your ViewModel is manipulating the View layer, which is an MVVM violation.
Reed's answer is correct, and is one way to solve your problem, create the ViewModel of the control in the MainWindow, hook up the events and bind the ViewModel to the user control via a DependencyProperty.
To allow the binding of the ViewModel to work, make sure you do not set the DataContext in the Constructor of the UserControl or on the Root element of the Xaml of the UserControl. Instead, set the DataContext on the first content element of the UserControl. This will allow external bindings to the UserControl to continue working while the DataContext of the UserControl is what you want.
<UserControl x:Class="StackOverflow._20914503.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:this="clr-namespace:StackOverflow._20914503"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
</Grid>
</UserControl>
With regards to swapping controls in and out, Reed is again correct and DataTemplates are the way to go.
Another way to solve your communications problems is to use RoutedEvents. Create a RoutedEvent with in the application and since the event will have no real association to a ui element, lets create a class to publish the routed event.
public static class EventManagement
{
public static readonly RoutedEvent ChangeViewEvent = EventManager.RegisterRoutedEvent("ChangeView", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
}
Now, in each of the UserControls (and it must be done within the code behind of a UserControl), you can call RaiseEvent which is implemented in the UIElement class. In the following code, I am picking up an event from the ViewModel of my UserControl and firing the RoutedEvent
private void ViewModel_ChangeEvent(object sender, EventArgs e)
{
RaiseEvent(new RoutedEventArgs(EventManagement.ChangeViewEvent));
}
In my main window, without know where the RoutedEvent is going to be fired from, I can add a handler to the Routed event like so
public MainWindow()
{
InitializeComponent();
this.AddHandler(EventManagement.ChangeViewEvent, new RoutedEventHandler(SomeControl_ChangeView));
}
private void SomeControl_ChangeView(object sender, RoutedEventArgs routedEventArgs)
{
}
.Net will handle the routing of the event for you based on the RoutedEvent registration.
The advantage of this approach is the separation the functionality. Everything works without knowing anything else. You can use triggers to insert UserControl into the MainWindow, they can all Raise the same RoutedEvent, and the MainWindow will handle them all.
To Summarise the flow of control. The ViewModel of the UserControl raises a standard CLR event that the UserControl handles. The UserControl Raises the RoutedEvent. .Net Bubbles the event up to the main window. The main window receives the event via its handler.
A couple of points to note.
1. The default routing strategy for RoutedEvents is Bubbling (from lowest element, say a button, to the highest, say MainWindow).
1. An event will stop once a handler has flagged the event as Handled.
1. Routing is mostly done via the Visual Tree.
If necessary, I can post the component parts of my example.
I hope this helps.
I have this simple UserControl:
<UserControl x:Class="WPFTreeViewEditing.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="Hello, world!" KeyDown="TextBlock_KeyDown" />
</Grid>
</UserControl>
I want to handle TextBlock.KeyDown event. So, I've added an event handler to the code-behind:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void TextBlock_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show("Key up!");
}
}
but it doesn't fire. What's wrong?
UPDATE.
PreviewKeyDown doesn't fire too.
This UserControl is used in HierarchicalDataTemplate then:
<Window x:Class="WPFTreeViewEditing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTreeViewEditing"
Title="MainWindow" Height="265" Width="419">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ViewModel}" ItemsSource="{Binding Items}">
<local:UserControl1 />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
From the documentation for UIElement.KeyDown:
Occurs when a key is pressed while focus is on this element.
You're using TextBlock which doesn't have the focus, so your KeyDown event will be handled by another control.
You can switch to TextBox and appy some styles so it'll look and behave like TextBlock, but you'll be able to get the focus and handle the event.
You should use PreviewKeyDown event instead of KeyDown event.
Ok, even though this question was posted a long time ago I had the same problem and found a way to get KeyDown events working, though it might not be what you're looking for I'll post the code to help future people with the same problem.
First thing first a KeyDown event handler in an Xaml Object will only fire off if that object has focus. Therefore you need a CoreWindow event handler, it's kind off the same thing but it will always run no matter what object or thing has focus. The following will be the code.
//CLASS.xaml.h
ref class CLASS{
private:
Platform::Agile<Windows::UI::Core::CoreWindow> window;
public:
void KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args);
//CLASS.xaml.cpp
CLASS::CLASS(){
InitializeComponent();
window = Window::Current->CoreWindow;
window->KeyDown += ref new TypedEventHandler
<Windows::UI::Core::CoreWindow^, Windows::UI::Core::KeyEventArgs^>(this, &CLASS::KeyPressed);
};
void CLASS::KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args){
SimpleTextBox->Text = Args->VirtualKey.ToString();
};
Basically you want a value to hold your window and use that to create a new TypedEventHandler. For safety you'll generally want to do this in your class' constructor a function that's only called once the moment the class starts (I still prefer the constructor though).
You can use this method to create an event handler for any event. Just change the "KeyDown" for another attribute like KeyUp, PointerMoved, PointerPressed and change the "&CLASS::KeyPressed" to the name of the function you want to be fired the moment you get an event of a corresponding type.