I am using Prism 7.1 navigation framework (WPF) to get a dialog window to pop up using the configuration below. This is successful. However, I want this popup to have tabs that I can navigate back and forth among. When I click the button on the popup box in an attempt to display ViewA inside of it, nothing happens. By setting a breakpoint, I see that the navigation path is hit, and is displaying the correct view name. Refer to PopUpWindow.cs. However when it goes to resolve the view, the view does not display. Even worse, no error is thrown! I am confused as to why this is occurring.
Assuming my namespaces are correct, what am I doing wrong?
PrismApplication.cs
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
}
//Have tried register type, register type for navigation, etc etc.
MainWindowViewModel.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Height="350" Width="525">
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest}">
<prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" />
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
<StackPanel>
<Button Margin="5" Content="Raise Default Notification" Command="{Binding NotificationCommand}" />
</StackPanel>
MainWindowViewModel.cs
public MainWindowViewModel
{
public InteractionRequest<INotification> NotificationRequest { get; set; }
public DelegateCommand NotificationCommand { get; set; }
public MainWindowViewModel()
{
NotificationRequest = new InteractionRequest<INotification>();
NotificationCommand = new DelegateCommand(RaiseNotification);
}
void RaiseNotification()
{
NotificationRequest.Raise(new PopupWindow());
}
}
PopUpWindow.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Height="350" Width="525">
<DockPanel LastChildFill="True">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="5" >
<Button Command="{Binding NavigateCommand}" CommandParameter="ViewA" Margin="5">Navigate to View A</Button>
</StackPanel>
<ContentControl prism:RegionManager.RegionName="ContentRegion" Margin="5" />
</DockPanel>
</UserControl>
PopUpWindow.cs
public class PopupWindowViewModel
{
private readonly IRegionManager _regionManager;
public DelegateCommand<string> NavigateCommand { get; private set; }
public PopupWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string navigatePath)
{
if (navigatePath != null)
_regionManager.RequestNavigate("ContentRegion", navigatePath);
//During debugging, this correctly shows navigatePath as "ViewA"
}
}
ViewA.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBlock Text="ViewA" FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
Maybe it's just not finding your view.
Isn't the second parameter supposed to be a url rather than a string?
From here:
https://prismlibrary.github.io/docs/wpf/Navigation.html
IRegionManager regionManager = ...;
regionManager.RequestNavigate("MainRegion",
new Uri("InboxView", UriKind.Relative));
Check where your view is and what the path should be.
I think you could prove that using something like:
var testinstance = System.Windows.Application.LoadComponent(testUrl);
https://learn.microsoft.com/en-us/dotnet/api/system.windows.application.loadcomponent?view=netframework-4.7.2
And if you're using MEF I think you also need to mark the View with the Export attribute.
Hopefully your problem is just you forgot about a folder or some such.
If not then it could be related to regionmanager not getting a reference to your region.
Regions that aren't in the visual tree are ignored by the region manager. You define ContentRegion within the PopUpWindow (which is lazily created), so it is not there and the navigation request for the unknown region is just ignored.
As detailled here and there, in this case, you have to add the region manually in the constructor of the view containing it:
RegionManager.SetRegionName( theNameOfTheContentControlInsideThePopup, WellKnownRegionNames.DataFeedRegion );
RegionManager.SetRegionManager( theNameOfTheContentControlInsideThePopup, theRegionManagerInstanceFromUnity );
with a region manager from the ServiceLocator:
ServiceLocator.Current.GetInstance<IRegionManager>()
The InteractionRequest pattern is a bit quirky. You need to make sure that all views that should react on the request have the necessary InteractionRequestTrigger in the visual tree. Thus, the immediate fix to your problem is to copy your XAML from MainWindowView.xaml to ViewA.xaml:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Height="350" Width="525">
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest}">
<prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" />
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
<!-- ... -->
</UserControl>
Then make sure to add the NotificationRequest in the viewmodel for ViewA. Please note that you may still encounter scenarios where the interaction request doesn't work. E.g. when adding triggers inside a data template. Though, as long as you put them on the UserControl level you should be fine.
One possible improvement to this (flawed) design is to create a behavior where you programmatically add these interaction triggers.
Related
I'm building a WPF app with custom UserControls, and I'm trying to understand how property bindings are supposed to work. I can't get even the most basic binding to work, and it's simple enough to distill into a tiny example, so I figured someone with more WPF experience might be able to put me on the right track.
I've defined a custom UserControl called TestControl, which exposes a Foo property, which is intended to be set in XAML whenever a UserControl is placed.
TestControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace BindingTest
{
public partial class TestControl : UserControl
{
public static readonly DependencyProperty FooProperty = DependencyProperty.Register("Foo", typeof(string), typeof(TestControl));
public string Foo
{
get { return (string)GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
public TestControl()
{
InitializeComponent();
}
}
}
The markup for TestControl just defines it as a control with a single button, whose label text displays the current value of the Foo property:
TestControl.xaml
<UserControl x:Class="BindingTest.TestControl"
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:local="clr-namespace:BindingTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button Content="{Binding Foo}" />
</Grid>
</UserControl>
In my MainWindow class, I just place a single instance of TestControl with its Foo property set to "Hello".
MainWindow.xaml
<Window x:Class="BindingTest.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"
xmlns:local="clr-namespace:BindingTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:TestControl Foo="Hello" />
</Grid>
</Window>
I would expect that when I build and launch this app, I'd see a window with a single button reading "Hello". However, the button is blank: the Binding doesn't seem to work.
If I add a click handler to the TestControl's button, I can verify that the value is being updated behind the scenes:
// Added to TestControl.xaml.cs:
private void Button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("Button clicked; Foo is '{0}'", Foo);
}
// Updated in TestControl.xaml:
// <Button Content="{Binding Foo}" Click="Button_Click" />
When I click the button, I get Button clicked; Foo is 'Hello', but the GUI never updates. I've tried using Path=Foo, XPath=Foo, etc., as well as setting UpdateSourceTrigger=PropertyChanged and verifying updates with NotifyOnTargetUpdated=True... nothing seems to result in the text in the UI being updated to match the underlying property value, even though the property value seems to be getting updated just fine.
What am I doing wrong? I feel like there's just a simple and fundamental misunderstanding in how I'm approaching this.
edit:
Poking around a bit more and reading similar questions has led me to a potential fix: namely, adding a name to the root UserControl element in TestControl.xaml (x:Name="control"), and changing the binding to explicitly specify that control ({Binding Foo, ElementName=control}).
I'm guessing that by default, {Binding Foo} on the Button element just means "find a property named 'Foo' on this Button control", whereas I'd assumed it'd mean "find a property named 'Foo' in the context that this Button is being declared in, i.e. on the TestControl".
Is specifying an explicit ElementName the best fix here?
You have to set the source object of the Binding to the UserControl instance, e.g. like this:
<Button Content="{Binding Foo, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
or
<UserControl ... x:Name="theControl">
...
<Button Content="{Binding Foo, ElementName=theControl}"/>
If you have many such Bindings, you may also set the DataContext of the top level element in the UserControl's XAML to the UserControl instance:
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<Button Content="{Binding Foo}" />
<Button Content="{Binding Bar}" />
</Grid>
You must however avoid to set the DataContext of the UserControl (which is often recommend by "expert" bloggers), because that would break DataContext-based Bindings of the UserControl properties like
<local:TestControl Foo="{Binding SomeFoo}" />
I am trying to use RoutedCommands in my UserControls, following the example in this article:
https://joshsmithonwpf.wordpress.com/2008/03/18/understanding-routed-commands/
I defined the RoutedCommand and CommandBindings in the UserControl instead of in the article's example. I am trying to use it in my MainWindow, so that when the Button is clicked, the Command in the UserControl is executed. However, the Button is disabled and the Foo_CanExecute() method is never executed.
<UserControl x:Class="RoutedCommandTest.ViewControl"
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:local="clr-namespace:RoutedCommandTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.CommandBindings>
<CommandBinding
Command="{x:Static local:ViewControl.Foo}"
PreviewCanExecute="Foo_CanExecute"
PreviewExecuted="Foo_Executed"
/>
</UserControl.CommandBindings>
<Grid>
</Grid>
</UserControl>
Here is the code for ViewControl.xaml.cs:
public static readonly RoutedCommand Foo = new RoutedCommand();
void Foo_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
void Foo_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("The Window is Fooing...");
}
public ViewControl()
{
InitializeComponent();
}
And here is the code for MainWindow.xaml:
<Window x:Class="RoutedCommandTest.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"
xmlns:local="clr-namespace:RoutedCommandTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:ViewControl/>
<Button Content="Foo" Margin="0 5" Command="{x:Static local:ViewControl.Foo}"/>
</Grid>
</Window>
I would like to know how to fix the issue so that the Button is enabled and the Foo_CanExecute() method is executed when the Button is clicked.
Your command is in a usercontrol, whilst the button is in mainwindow.
Which presumably contains your usercontrol.
Like bubbling and routing events ( which are used to drive them ).
Executed looks for the command bubbling UP the visual tree to the binding.
PreviewExecuted looks for the command tunnelling DOWN the visual tree to the binding.
Since your button is in the parent of the usercontrol I'm not sure whether either bubbling or tunnelling will work.
But tunnelling would be PreviewExecuted And PreviewCanExecute.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.commandbinding.previewexecuted?view=netframework-4.8
https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.commandbinding.previewcanexecute?view=netframework-4.8
Routedcommands can be pretty tricky to get right.
One thing you sometimes have to do is to bind commandtarget to tell it where to go look.
eg:
<Grid>
<local:UserControl1 x:Name="UC1" Height="60" Width="100"/>
<Button Content="Foo" TextElement.FontSize="30" Command="{x:Static local:UserControl1.Foo}"
CommandTarget="{Binding ElementName=UC1}"
/>
</Grid>
Works for me.
I have rarely found them useful - this is one of the aspects makes them way less useful than you might at first imagine.
EDIT:
It's perhaps worth mentioning the other thing makes these unattractive compared to a regular icommand. You need to either use a static which means it's only suitable for very generic commands OR you need event handlers which will be in code behind.
On the other hand.
If you're writing something has to work generically with whatever has focus. Like say a text editor with multiple textboxes and you're doing text manipulation. A routed command might be suitable. I have never encountered such a requirement in apps I've worked on though.
I'm writing a small application whilst learning MVVM in WPF.
As long as I keep on using one Window, everything is pretty easy.
Now I want to open a new Window with a specific ViewModel.
I have a main ViewModel, which contains a Command that should open a new Window / ViewModel, along with a Parameter.
To do this in an MVVM way, I've created a NavigationService, which I'd like to call like this:
public MainWindowViewModel()
{
DetailsCommand = new DelegateCommand(Details);
}
public void Details()
{
SessionsViewModel sessions = new SessionsViewModel();
_NavigationService.CreateWindow(sessions);
}
I've noticed that it's possible to "bind" Views and ViewModels in XAML, like this:
<Application x:Class="TimeTracker.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TimeTracker"
xmlns:vm="clr-namespace:TimeTracker.ViewModels"
xmlns:vw="clr-namespace:TimeTracker.Views"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vm:MainWindowViewModel}">
<vw:MainWindow />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SessionsViewModel}">
<vw:Sessions />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
<Window x:Class="TimeTracker.Views.Sessions"
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"
xmlns:local="clr-namespace:TimeTracker.Views"
xmlns:vm="clr-namespace:TimeTracker.ViewModels"
mc:Ignorable="d"
Title="Sessions" Height="300" Width="300">
<Window.DataContext>
<vm:SessionsViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text="Hallo" />
</Grid>
</Window>
The problem I'm having is that I don't know how I can use this ResourceDictionary in my NavigationService, so that I can create a new Window by only using its ViewModel.
class NavigationService
{
public void CreateWindow(IViewModel viewModel)
{
//How do I create a new Window using the ResourceDictionary?
}
}
Make sure you have a ContentControl or ContentPresenter in your new window so that the ViewModel can be presented. Next, make sure that resource dictionary is in scope. Putting it in Application.Resources will make it global and guarantee that WPF can find the DataTemplate.
Also, don't use a Window class as your view in the DataTemplate. Use your sub-window panel (e.g., Grid, StackPanel, etc).
I do this:
<blah:MyChildWindow>
<ContentControl Content={Binding DataContext}/>
</blah:MyChildWindow>
And in Application.Resources:
<DataTemplate DataType={x:Type blah:MyViewModel}>
<blah:MyChildWindow/>
</DataTemplate>
BTW - using DataTemplates the way you are trying to do is an excellent pattern.
I'm new to WPF and I'm trying to dynamically add a Button inside a ContentControl, which should fire a command when clicked. I'm using MVVMLight to handle the Commands.
Below I have an example with two buttons. The top button is placed directly into the StackPanel. This button fires off the Command as expected.
The second button is placed inside a ContentControl. It displays correctly, but the Command does not fire when the button is clicked.
I assumed this is because the Binding does not transfer down through the DataTemplate, but it seems to work if I use regular Commands instead of MVVMLight RelayCommands.
I don't want to remove the framework, so I'm wondering if anyone knows how to fix it? Thanks
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="MyButton" >
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<!--When this button is clicked, the Command executes as expected-->
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
<!--Nothing happens when this button is clicked-->
<ContentControl ContentTemplate="{StaticResource MyButton}"/>
</StackPanel>
</Window>
Here's the ViewModel with the command:
public class MainViewModel : ViewModelBase
{
public ICommand MyCommand { get; private set; }
public MainViewModel()
{
MyCommand = new RelayCommand(MyCommand_Executed, MyCommand_CanExecute);
}
private bool MyCommand_CanExecute()
{
return true;
}
private void MyCommand_Executed()
{
MessageBox.Show("The command executed");
}
}
The problem here is the implicit DataContext in ContentTemplate is the Content and this has not been set to anything. You need to set Content to some Binding to bridge the DataContext currently in the visual tree, something like this:
<ContentControl ContentTemplate="{StaticResource MyButton}" Content="{Binding}"/>
Another solution is to give your Window a name:
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel"
x:Name="_this">
Then bind via its context instead:
<Button Content="SUBMIT" Command="{Binding ElementName=_this, Path=DataContext.MyCommand}" Width="200" Height="50"/>
This is particularly handy for things like ListViews and ItemControls, as their DCs get set to the list elements. Keep in mind though that this will only work on members within the same visual tree, if that's not the case (e.g. popup menus etc) then you need to proxy a binding as described in this article.
I'm just starting out with WPF having used WinForms for some time and seem to have fallen at the first hurdle.
I have my main XAMLdefined as
<Window x:Class="FHIRCDALoader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FHIRCDALoader.xaml"
Title="FHIR CDA Loader" Height="350" Width="525"
Icon="Icons/color_swatch.png">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New"
Executed="NewDocument" />
</Window.CommandBindings>
<DockPanel>
<local:menubar DockPanel.Dock="Top"/>
<local:toolbar DockPanel.Dock="Top"/>
<local:statusbar DockPanel.Dock="Bottom" />
<RichTextBox x:Name="Body"/>
</DockPanel>
</Window>
Note the use of the user controls, one of which is the "statusbar"
<UserControl x:Class="FHIRCDALoader.xaml.statusbar"
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">
<StatusBar >
<StatusBarItem>
<TextBlock x:Name="bbstatusbar" />
</StatusBarItem>
</StatusBar>
</UserControl>
So in MainWindow.xaml.cs I see I can reference RichTextBox named body from the main XAML file. I can't however reference the TextBlock in the UserControl which is named "bbstatusbar".
How do I set the value of the TextBlock from MainWindow.xaml.cs?
In agreement with Vlad and HighCore's comments: you don't set the TextBlock from MainWindow.xaml.cs. You bind it to a view-model. A binding simply looks like this:
<TextBlock Text="{Binding StatusText}" />
The above says: bind the Text property to a property in the current data-context called "StatusText". Next, create a view model:
public class ViewModel : INotifyPropertyChanged
{
public string StatusText
{
get { return _statusText; }
set
{
_statusText = value;
RaisePropertyChanged("StatusText");
}
}
// TODO implement INotifyPropertyChanged
}
Finally, set the DataContext of your MainPage to the view model. You can do this in a variety of ways, but let's say here for simplicity, do it in the constructor:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel { StatusText = "hello world" };
}
Now, the idea is to put your model-related logic into ViewModel. So, you shouldn't need to access the UI elements -- instead, update the view-model properties that the UI elements are bound to.