I'm using the MVVM pattern so my view-model doesn't know anything about the view, and the view is displayed via DataTemplates.
When the view isn't displayed anymore, I want to take a screenshot of it (with a utility class). So I want to bind to FrameworkElement.Unloaded, and when its hit, take a screenshot of the usercontrol for use in another control to select which view to go to.
I read this article, which makes it appear as if attached properties would work (I'm using it on the UserControl object)
http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html
I get the error that a binding can only be set on a DependencyObject or DependencyProperty. I followed his instructions properly. Any idea why this isnt working or how I can bind to that in a MVVM scenario?
Is it not possible to bind to that particular event or to an event in the root xaml node?
Here's teh code (in addition to the EventBehaviorFactory in the link above)
public static class FrameworkElementBehavior
{
public static readonly DependencyProperty UnloadedCommandProperty = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(FrameworkElement.UnloadedEvent, "UnloadedCommand", typeof(FrameworkElementBehavior));
public static void SetUnloadedCommand(DependencyObject o, ICommand value)
{
o.SetValue(UnloadedCommandProperty, value);
}
public static ICommand GetUnloadedCommand(DependencyObject o)
{
return o.GetValue(UnloadedCommandProperty) as ICommand;
}
}
<UserControl x:Class="WTF.BuxComp.Prism.Modules.Win.OrderEntryPos.Views.CustomerView"
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:WTF.BuxComp.Prism.Modules.Win.OrderEntryPos.Helpers"
mc:Ignorable="d"
d:DesignHeight="510" d:DesignWidth="716"
local:FrameworkElementBehavior.UnloadedCommand="{Binding UnloadedCommand}">
and the exact error is
A 'Binding' cannot be set on the 'SetUnloadedCommand' property of type
'CustomerView'. A 'Binding' can only be set on a DependencyProperty of
a DependencyObject.
The best thing I can suggest is to map to a regular event handler then call OutOfViewCommand.Execute from within your control to your DataContext. You will also need to map UserControl.DataContextChanged on your control and save your datacontext locally.
public partial class MainWindow : Window
{
private object Data { get; set; }
public MainWindow()
{
InitializeComponent();
}
private void Window_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
this.Data = e.NewValue;
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
{
if(this.Data != null)
this.Data.OutOfViewCommand.Execute(null);
}
}
XAML:
<Window x:Class="WpfApplication3.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" DataContextChanged="Window_DataContextChanged" FrameworkElement.Unloaded="Window_Unloaded">
<Grid>
</Grid>
Though this does not strictly conform with MVVM, a compromise you will often face with framework calls, it still works in a re-usable way with any view model.
For this you may need to correctly name your attached proerty.... its name declared is OutOfViewCommand but it should be UnloadedCommand
public static class FrameworkElementBehavior
{
public static readonly DependencyProperty UnloadedCommandProperty =
EventBehaviourFactory.CreateCommandExecutionEventBehaviour
(FrameworkElement.UnloadedEvent,
"UnloadedCommand",
typeof(FrameworkElementBehavior));
public static void SetUnloadedCommand
(DependencyObject o, ICommand value)
{
o.SetValue(UnloadedCommandProperty, value);
}
public static ICommand GetUnloadedCommand
(DependencyObject o)
{
return o.GetValue(UnloadedCommandProperty) as ICommand;
}
}
Related
I'm creating a UserControl in WPF, that is able to work for any object of type IMyNode. Basically, it receives an ObservableCollection through a dependency property, register to it and do some stuff.
In one of my usecase, I use in a control that uses(and need), an ObservableCollection of SomeSpecificNode. SomeSpecificNode is an implementation of IMyNode.
Currently, I've a binding error:
System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'System.Collections.ObjectModel.ObservableCollection`1[SomeSpecificNode]' and 'System.Collections.ObjectModel.ObservableCollection`1[IMyNode]'.
I understand why it happens, it doesn't know how to convert automatically an ObservableCollection<SomeSpecificNode> to ObservableCollection<IMyNode>.
What would be the correct approach to do this?
Using a converter would break the NotifyPropertyChange. Using a ObservableCollection<IMyNode> in my parent ViewModel would not work for the other control in the same page.
Thank you!
Here some pseudo code:
public class SomeSpecificNode: IMyNode{
}
public interface IMyNode{
}
public class ParentViewModel {
public ObservableCollection<SomeSpecificNode> SelectedNodes {get;}=> new ObservableCollection<SomeSpecificNode>()
}
<UserControl x:Class="ParentView"
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:ch.VibroMeter.Xms.Configurators.Controls.ActionBar"
xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<ParentViewModel/>
</UserControl.DataContext>
<StackPanel Orientation="Horizontal" Name="RootContainer">
<SomeChildControl Nodes="{Binding SelectedNodes}" /><!-- This binding will fail !-->
</StackPanel
</UserControl>
public partial class ParentView : UserControl
{
public static readonly DependencyProperty NodesProperty = DependencyProperty.Register(
nameof(Nodes), typeof(ObservableCollection<IMyNode>), typeof(ParentView), new PropertyMetadata(default(ObservableCollection<IMyNode>), OnNodesChanged));
private static void OnNodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//...
}
public ObservableCollection<IMyNode> Nodes
{
get { return (ObservableCollection<IMyNode>)GetValue(NodesProperty); }
set { SetValue(NodesProperty, value); }
}
}
You should change the type of the dependency property to a compatible type such as IEnumerable<IMyNode>.
You cannot set an ObservableCollection<IMyNode> property to anything else than an ObservableCollection<IMyNode> or null.
An ObservableCollection<SomeSpecificNode> is not an ObservableCollection<IMyNode> but it is an IEnumerable<IMyNode> assuming that SomeSpecificNode implements IMyNode.
So this compiles just fine;
IEnumerable<IMyNode> collection = new ObservableCollection<SomeSpecificNode>();
But this doesn't:
ObservableCollection<IMyNode> collection = new ObservableCollection<SomeSpecificNode>(); //Cannot implictly convert type...
The difference is that IEnumerable<T> is covariant. Please refer to the docs for more information.
Similar questions have been asked for WinForms projects, but not for WPF projects.
This VERY simple sample below shows the issue. The real user control I have is much more complicated but this sample shows the basic issue I am having.
The UserControl1 xaml (nothing there as it doesn't need anything to show the issue)
<UserControl x:Class="TestProperty.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="450" d:DesignWidth="800">
<Grid>
</Grid>
</UserControl>
The Window xaml (just contains a UserControl1):
<Window
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:TestProperty="clr-namespace:TestProperty;assembly=TestProperty" x:Class="TestApp.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TestProperty:UserControl1 HorizontalAlignment="Left" Height="100" Margin="10,10,0,0" VerticalAlignment="Top" Width="100"/>
</Grid>
</Window>
The actual Window code-behind is really nothing
using System.Windows;
namespace TestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Here is code-behind for UserControl1. Has two properties where the first property simply modifies the second to be the same.
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace TestProperty
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
[Description("Sets a test property value that sets another property.")]
[Category("UserControl1")]
[RefreshProperties(RefreshProperties.All)]
public bool TestValue1
{
get => (bool) GetValue(TestValue1Property);
set => SetValue(TestValue1Property, value);
}
public static readonly DependencyProperty TestValue1Property =
DependencyProperty.Register(
"TestValue1", typeof(bool), typeof(UserControl1),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(TestValue1Changed)));
private static void TestValue1Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
((UserControl1) d)?.TestValue1Changed(e);
private void TestValue1Changed(DependencyPropertyChangedEventArgs e)
{
TestValue2 = (bool) e.NewValue;
InvalidateProperty(TestValue2Property);
}
[Description("Value should change automatically when TestValue1 changes.")]
[Category("UserControl1")]
[RefreshProperties(RefreshProperties.Repaint)]
public bool TestValue2
{
get => (bool) GetValue(TestValue2Property);
set => SetValue(TestValue2Property, value);
}
public static readonly DependencyProperty TestValue2Property =
DependencyProperty.Register(
"TestValue2", typeof(bool), typeof(UserControl1),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(TestValue2Changed)));
private static void TestValue2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
((UserControl1) d)?.TestValue2Changed(e);
private void TestValue2Changed(DependencyPropertyChangedEventArgs e)
{
InvalidateProperty(TestValue2Property);
}
}
}
The attribute RefreshProperties and the call to InvalidateProperty appear to do nothing.
When editing the MainWindow.xaml and selecting UserControl1, the UserControl1 section of the Properties window shows TestValue1 and TestValue2. If TestValue1 is changed, TestValue2 does NOT change. However, if you change to another Visual Studio (or Blend) editor window and then back to the MainWindow.xaml, then TestValue2 will be the same as TestValue1.
I cannot seem to find any method to automatically have TestValue2 update when TestValue1 forces the update. The real case is obviously more complex with quite a few controls and when adding our UserControl, we want the designer to be able to specify several properties that can result in forcing (or coercing) other properties. However, the designer needs to be able to see the property value changes without having to switch to another file and back again to redraw the designer's properties window.
Is this something that simply can't be done - or is there something I'm missing. Note that this is a UserControl and is not designed to operate itself MVVM, but allow another window to be designed using the control with (or without) MVVM design.
Also, in a 'kind of' related issue. We have one property that needs to have an entry in the properties the same as Width and Height with the auto button. When set to Auto display "Auto (nnn)" as the automatically set value changes nnn changes. When not set, the value is simply nnn and is defined during the design of the window using the UserControl. Is this possible?
Thanks in advance to anyone who can shed light on this.
I'm implementing a class that contains an ObservableCollection and in my XAML I have a polyline.
I successfully binded the XAML dataContext to the this class and the polyline to the Observable collection. But now, of course, I'm facing a wrong type conversion.
I found an example of value converter and I added it to my code but I'm not able to add it as a resource in my XAML ...
The overall structure looks like that
public class externalClass
{
public ObservableCollection<Point> debugCh1 { get; set; }
public void test()
{
... performo modifications
on debugCh1 for testing purposes...
}
public class PointCollectionConverter : IValueConverter
{
.. implements convert and cnverBack
}
}
For the XAML
<Window x:Class="tester.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="277" Width="525" xmlns:my="clr-namespace:binding;assembly=binding" xmlns:my1="clr-namespace:deviceManager;assembly=deviceManager" Closing="Window_Closing">
<Window.Resources>
<local:PointCollectionConverter x:Key="pointCollectionConverter"/>
</Window.Resources>
... The window Itself ...
<Polyline Points="{Binding debugCh1}" />
...
</Window>
And for the C# behind
public partial class MainWindow : Window
{
private externalClass toTest;
public MainWindow()
{
InitializeComponent();
DataContext = toTest;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
toTest.test();
}
}
The whole thing works nice but for the <local:PointCollectionConverter..
Compiler says The type 'local:PointCollectionConverter' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built.
Any suggestions on how to add this reference??
You just have to make sure the namespaces are lined up. I'd start by separating your converter class from the externalClass class (I'm not sure it's even possible to reference nested-classes like that from XAML):
namespace MyCompany.MyProject
{
public class PointCollectionConverter : IValueConverter
{
.. implements convert and cnverBack
}
}
Now you can define the local xmlns and link it to MyCompany.MyProject:
<Window xmlns:local="clr-namespace:MyCompany.MyProject"
And with that the converter should be accessible as written.
<local:PointCollectionConverter x:Key="pointCollectionConverter"/>
I've been trying to resolve this issue for some time.
I'm trying to bind a TextBlock's text to a string property using the INotifyPropertyChanged interface. For some reason the PropertyChangedEventHandler is always null when the value of the property is changed thus the target never gets updated...
Any suggestions?
Code below:
XAML code:
<UserControl x:Class="MoleDashboard.MainPage"
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:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:datacontrols="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
xmlns:prop="clr-namespace:MoleDashboard"
<UserControl.Resources>
<prop:YearScreen x:Key="YearScreenProps"/>
</UserControl.Resource>
<TextBlock Margin="10 5" x:Name="DataGridLabel" Visibility="Visible" Text="{Binding YearProperty, Source={StaticResource YearScreenProps}, Mode=OneWay}"/>
Bound property code:
public class YearScreen : INotifyPropertyChanged
{
private string metricProperty;
private string yearProperty;
public event PropertyChangedEventHandler PropertyChanged;
public YearScreen()
{
}
public string YearProperty
{
get { return yearProperty; }
set { yearProperty = value; this.OnPropertyChanged("YearProperty"); }
}
public string MetricProperty
{
get { return metricProperty; }
set { metricProperty = value; this.OnPropertyChanged("MetricProperty"); }
}
public void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
This is based on the comments provided and not just the question above.
Basically the problem is that you are creating updating a second instance of your ViewModel (called a YearScreen in your code) and updating that.
A YearScreen is already being created and bound to your Xaml, by the inclusion of:
<UserControl.Resources>
<prop:YearScreen x:Key="YearScreenProps"/>
</UserControl.Resource>
You are then creating a second ViewScreen elsewhere in code (via new ViewScreen()) and updating that, however there is no connection between the 2 ViewScreen instances, so nothing will update in the Xaml page.
One Possible (quick) solution:
Create your YearScreen as a singleton. That is add a static accessor of type YearScreen in the class and set it from the constructor.
public class YearScreen : INotifyPropertyChanged
{
private static YearScreen _This;
public static YearScreen This { get { return _This; } }
[snip]
public YearScreen()
{
_This = this;
}
The you can access the "single" instance of your YearScreen from elsewhere using the static singleton accessor e.g.:
YearScreen.This.YearProperty = DateTime.Now.ToString():
There are better patterns for sharing ViewModels than singletons, but that will get you going.
The pattern you started with is ViewFirst creation (the view creates the ViewModel). ModelFirst does the opposite, but is bad as the model knows how it is displayed. Using a controller object to create the View and ViewModel and connect them is a better alternative, but that is then getting quite complicated. Using injection of single instance objects is a better option, but involves a whole load of new concepts. Lookup Silverlight Prism after you solve your current problems.
Instead of creating the ViewModel in the resources you should set it into the DataContext of the view from external code.
If you really want to put it in the Resources like that you can get it out of the resources in the code behind Loaded method or in the constructor after the initializecomponent call. Like so:
private YearScreen model;
public MainPage()
{
this.Loaded += MainPage_Loaded;
this.InitializeComponent();
}
void MainPage_Loaded(object sender, EventArgs e)
{
this.model = (YearScreen)this.Resources["YearScreenProps"];
}
Maybe expose it as a property so you can then access it externally. But personally I'd rather create the model externally than pass it into the View instead. Put it into the DataContext.
I am building a composite application using CAL/Prism. The main region is a tab control, with multiple types of views in it. Each view has a custom set commands that it can handle which are bound to toolbar buttons at the top of the window. I've done this before in non-CAL apps by simply setting the InputBinding on the command, but I haven't been able to find any such mechanism in the source code for the CAL modules.
My question is, what is the best way to hook up a keystroke to my view, so that when the user presses Alt + T, the associated DelegateCommand object handles it? Hooking up a shortcut can't be THAT difficult...
Just for reference, the CommandReference class is currently not included in an assembly that you can reference, but is included with the M-V-VM project template. So if you don't build your application from the template, then you have to get the class from somewhere else. I chose to copy it from the sample project. I included it below to allow everyone easy access to this little chunk of goodness, but be sure to check for updates to the template in future versions of the M-V-VM Toolkit.
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when data-binding from XAML.
/// </summary>
public class CommandReference : Freezable, ICommand
{
public CommandReference( )
{
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( "Command", typeof( ICommand ), typeof( CommandReference ), new PropertyMetadata( new PropertyChangedCallback( OnCommandChanged ) ) );
public ICommand Command
{
get { return (ICommand)GetValue( CommandProperty ); }
set { SetValue( CommandProperty, value ); }
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (Command != null)
return Command.CanExecute( parameter );
return false;
}
public void Execute(object parameter)
{
Command.Execute( parameter );
}
public event EventHandler CanExecuteChanged;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandReference commandReference = d as CommandReference;
if (commandReference != null)
{
ICommand oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
ICommand newCommand = e.NewValue as ICommand;
if (newCommand != null)
newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
}
}
#endregion
#region Freezable
protected override Freezable CreateInstanceCore( )
{
return new CommandReference();
}
#endregion
}
Enjoy!
The MVVM Toolkit has a class called a CommandReference that will allow you to use a reference to a command as a keybinding.
<Window ...
xmlns:toolkit="clr-namespace:CannotRememberNamspace;assembly=OrTheAssembly"
>
<Window.Resources>
<toolkit:CommandReference
x:Key="ExitCommandReference"
Command="{Binding ExitCommand}" />
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="X"
Modifiers="Control"
Command="{StaticResource ExitCommandReference}" />
</Window.InputBindings>
</Window>
This'll do it.
Edit: Since this was written, WPF 4.0 fixed this particular issue and you no longer have to use the static resource workaround. You can reference the command in your viewmodel directly from the KeyBinding.