Firstly, I am new to WPF and MVVM, am trying hard to write well structured/separated code so please be kind.
I have created a user control and its own separate view model. In the view model I have an ICommand which relays to a method in the same viewmodel. I bind to this command in the XAML using System.Windows.Interactivity on an event like so:-
<UserControl x:Class="MyNamespace.MyControl"
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyNamespace"
mc:Ignorable="d"
Height="300"
d:DesignHeight="300" d:DesignWidth="1500"
IsManipulationEnabled="True"
Background="{StaticResource BackgroundWhiteBrush}">
<Grid
d:DataContext="{x:Static local:MyControlDesignModel.Instance}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseUp">
<i:InvokeCommandAction Command="{Binding MyViewModelCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
My code behind (which I'm trying to leave as empty as possible) looks like this:
namespace MyNamespace
{
/// <summary>
/// Interaction logic for MyControl.xaml
/// </summary>
public partial class MyControl : UserControl
{
public MyControl()
{
DataContext = ViewModelMyControl;
InitializeComponent();
}
}
}
I want to be able to use this control in several pages. I also want to be able to call a method in this view model (passing a parameter) from other view models to allow it to update itself from a datastore.
I used a DI container to provide a reference to the view model so that I can a) reference its data loading method from another place and b) set that to the DataContext in the code-behind (above).
The implementation of the DI container provides this as follows:
/// <summary>
/// A shortcut to access the <see cref="MyControlViewModel"/>
/// </summary>
public static MyControlViewModel ViewModelMyControl => Framework.Service<MyControlViewModel>();
With this DI referenced viewmodel on the DataContext, the event/command does not fire.
If I change the code behind to as follows, the event/command does fire but then I lose the static reference which I was trying to have to "hold" the data between pages. I seem to be able to have events or static reference but not both.
namespace MyNamespace
{
/// <summary>
/// Interaction logic for MyControl.xaml
/// </summary>
public partial class MyControl : UserControl
{
public MyControl()
{
DataContext = new MyControlViewModel();
InitializeComponent();
}
}
}
I think it has something to do with the ViewModel lifecycle or perhaps binding in general. I have been following a lot of guides and now find myself stuck.
How do I have event/commands firing and also maintain a reference to the user control's data between pages which use it?
Related
I'm trying to refactor the property MyText to a new name HerText in the following solution:
MainWindow.xaml.cs
using System.Windows;
namespace resharper_refactoring_xaml
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyText = "Blabla";
DataContext = this;
}
public string MyText { get; set; }
}
}
MainWindow.Xaml
<Window x:Class="resharper_refactoring_xaml.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:resharper_refactoring_xaml"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=MyText}"></TextBlock>
</Grid>
</Window>
I right click on the property and select Refactor this > Rename. Then I type in a new name for the property, hit Next.
Unfortunately, only the references of MyText in the code-behind are renamed. References to MyText in the XAML file rename intact.
According to this question Resharper should be able to propagate refactorings to XAML files.
Why is the rename not propagating to the XAML file? Is there some sort of Resharper setting I might have overlooked?
The reason behind this seems to be that ReSharper cannot determine that the property name specified in the XAML markup refers to the property defined in the MainWindow class, if the DataContext property is set in code-behind.
Bindings refer to the DataContext of controls as source by default. If it is not detected, the link between the loose markup and the defining type is lost. I cannot tell if this is a bug in ReSharper or a general limitation.
However, there are two simple solutions to this issue that work for me:
Set a design time data context to the type that defines the property here MainWindow.
<Window ...
d:DataContext="{d:DesignInstance Type={x:Type local:MainWindow}}">
Set the data context via binding in XAML instead of code-behind.
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
I am trying to directly bind to a model (similar to the situation here), instead of the property on that model, but it is not working. I previously asked a question about this, and the answer had worked in UWP. I am now working in WinUI3 (packaged UWP) and am not sure if the same solution applies.
I have a class:
public class Message
{
public string Title { get; set; }
public string Content { get; set; }
}
I have a UserControl that displays that class:
public sealed partial class MessageControl : UserControl
{
/// <summary>
/// The message to display.
/// </summary>
public Message Message { get; set; }
public MessageControl()
{
this.InitializeComponent();
}
}
With XAML:
<UserControl
x:Class="MessageClient.Controls.EmailControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MessageClient.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<StackPanel>
<!-- Displays the title of the message. -->
<TextBlock Name="HeaderTextBlock"
Text="{x:Bind Message.Title, Mode=OneWay}"></TextBlock>
<!-- Displays the content of the message. -->
<TextBlock Name="ContentPreviewTextBlock"
Text="{x:Bind Message.Content, Mode=OneWay}"></TextBlock>
</StackPanel>
</Grid>
</UserControl>
Now, I have a Window I am displaying that control on, inside of a list view:
<Window
x:Class="MessageClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:EmailClient"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:MessageClient.Controls" xmlns:models="using:MessageClient.Models"
mc:Ignorable="d">
<Grid>
<!-- The list of messages. -->
<ListView x:Name="MessagesListView"
ItemsSource="{x:Bind Messages}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Message">
<controls:MessageControl Margin="5"
Message="{x:Bind Mode=OneWay}"></controls:MessageControl>
<!-- TEST 1 -->
<!--<TextBlock Text="{x:Bind Title}"></TextBlock>-->
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- TEST 2 -->
<!--<controls:MessageControl Message="{x:Bind TestMessage, Mode=OneWay}"></controls:MessageControl>-->
</Grid>
</Window>
And the code behind for the Window is:
public sealed partial class MainWindow : Window
{
public ObservableCollection<Message> Messages;
public Message TestMessage;
public MainWindow()
{
this.InitializeComponent();
// Populate the test message.
this.PopulateTestMessages();
}
private void PopulateTestMessages()
{
// The TestDataGenerator just returns a staticaly defined list of test messages.
this.Messages = new ObservableCollection<Message>(TestDataGenerator.GetTestMessages(10));
this.TestMessage = this.Messages.First();
}
}
When I run this code (which compiles and runs), I get a window with the expected number of items in the ListView (10), but they are all blank. There are also no XAML binding failures being reported (via the new feature in Visual Studio 2022). In an attempt to investigate this, I added in a textblock inside of the listview (denoted as TEST 1 in the comments) bound to the Title property of the Message. This worked as expected, displaying the correct titles for the 10 messages. I then added a single MessageControl to the Window outside of the ListView (denoted as TEST 2 in the comments) bound to a single Message property on the Window. This also worked exactly as expected.
Am I missing something? Why does the specific case of binding to a model directly (as opposed to a property on that model - i.e. a binding with no path) not work, when the data is there and works with other binding configurations?
Turns out this specific case exposes some of the ordering of how bindings work in XAML. To understand what is happening here:
The Window is being initialized, which initializes its UI, including the ListView.
The Message models are then retrieved and added to the ObservableCollection.
When each Message is added to the ObservableCollection, the ListView using that ObservableCollection as its ItemsSource then creates a new Item using the ItemTemplate specified in the XAML, and then initializes and binds the newly initialized MessageControl to the Message. The TEST 1 verifies this is happening.
The problem here is in step 3: The MessageControls are initialized AND THEN bound to. When the binding happens, the UI is never notified that it needs to update the binding. This is why adding Mode=OneWay to the {x:Bind} doesn't fix the issue. The OneWay mode tells the binding to update whenever the model updates... but the XAML is still never aware the model was updated.
I am not entirely sure why this is different than the other two bindings (the TEST 1 and TEST 2 comments), but this seems to be an issue with user-defined types (classes) specifically, and NOT with primitive or built-in type properties (such as string and int).
The fix is to enable the model to inform (or "notify") the UI that is has been updated. This is done be implementing the INotifyPropertyChanged interface. Update the MessageControl to:
public sealed partial class MessageControl : UserControl, INotifyPropertyChanged
{
private Message _message;
/// <summary>
/// The message to display.
/// </summary>
public Message Message
{
get { return this._message; }
set
{
this._message = value;
this.RaisePropertyChanged("Message");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public EmailControl()
{
this.InitializeComponent();
}
}
A final note: implementing INotifyPropertyChanged MUST be done on the MessageControl for this to work. Implementing INotifyPropertyChanged on the Message model would allow for the MessageControl (or any other data bound UI, such as the commented TEST 1) to update if a particular property of the Message model updates, but does not fix the issue of the Message model iself on the MessageControl being updated.
Let's say I have the following XAML :
<Window x:Class="Test.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">
<Canvas>
<Button Content="Write something" Canvas.Left="43" Canvas.Top="159" Width="162" Height="42" Click="Button_Click_1"/>
</Canvas>
My ViewModel can be this class, or can contain a instance of it (a viewmodel logic) :
//Here is my static class for extension methods
public static class ExtendenWindowClass
{
/// <summary>
/// Eventhandler for Button
/// </summary>
/// <param name="obj"></param>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void Button_Click_1(this MainWindow obj, object sender, RoutedEventArgs e)
{
MessageBox.Show("Wait 10 seconds");
Thread.Sleep(10000);
MessageBox.Show("Ready, now you can press again");
}
}
So the whiring is no longer in code behind, but in an extension method. The usage of static fields for MainWindow class is minimal, so it can be skipped.
The look of xaml is more natural than with DataBinding objects and curly braces. And I also follow separation of concepts .
What do you think ?
You are just doing it other way around.
There are multiple ways of extending a class. Two of them are -
Partial class.
Extension methods.
In code behind you are extending your class using partial class implementation.
public partial class MainWindow : Window { }
And in code you posted you are achieving that using other way i.e. extension methods. I don't think this way you are getting something more here.
MVVM pattern's main motive is to decouple UI logic from business logic. Also having code in extension method OR in code behind can't be unit tested at all. Code behind and having extension method on window is exactly same for me. Your View and ViewModel should work oblivious to each other so that it can be worked on simultaneously by different developers.
You can read about MVVM more here and here.
I would like to post my NServiceBus subscripiton messages derived from an EventHandler class to a ListView. The ListView is located inside the MainWindow.xaml of the WPF application.
Here is my NServiceBus subscription event handler code. Note: I would like to post the event message to the ListView control in MainWindow.xaml. Any ideas?
namespace EventPublisher.SubscriberDemoWPF
{
public class PublishTrackEventHandler : IHandleMessages<PublishTrackEvent>
{
public void Handle(PublishTrackEvent message)
{
Trace.TraceInformation(message.GetType().Name);
//Need to post event message to ListView control in MainWindow.xaml UI;
}
}
}
Here is my MainWindow.xaml code, which is in the same namespace as my event handler code:
<Window x:Class="EventPublisher.SubscriberDemoWPF.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">
<Grid>
<ListView Height="260" HorizontalAlignment="Left" Margin="12,12,0,0" Name="lstEvents" VerticalAlignment="Top" Width="479" />
</Grid>
</Window>
Here is the MainWindow.xaml.cs code (typical):
namespace EventPublisher.SubscriberDemoWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//Would normally use listview.items.add("messages");
}
}
From your NSB message handler you could fire an event that has been attached to from the Window. Depending on how you are managing threads, be aware of updating UI elements from threads other than the UI thread. Check out this article in MSDN for events in WPF.
I'm new to WPF data binding.
I have a ListBox on a form that I want to bind to the results of the following method call:
RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)
.OpenSubKey(#"SOFTWARE\Vendor\Product\Systems").GetSubKeyNames();
At the moment I'm doing it at runtime by assigning ListBox.ItemsSource = (method); in the Window_Loaded() event handler. But this means that the source data for the control is non-obvious when looking at the control configuration in the form editor.
Is there a way to configure this binding within the XAML so that it is visible in the form editor, to make the behavior of the code easier to understand?
Most of the examples in the MSDN documentation bind the controls to static resources, like in-line XAML resources. I've noticed that there is an ObjectDataProvider class which provides "[...] the ability to bind to the result of a method." However I am finding the examples in the ObjectDataProvider documentation quite confusing. I'd appreciate some advice on whether that's the right way to do this binding, and if so, what syntax to use when declaring the ObjectDataProvider.
In short, I don't think you can use such a complex statement directly in your XAML. As you've found, it is possible to bind to the result of calling a method of an object via ObjectDataProvider, but your expression is a chain of method calls that I believe cannot be used to source ObjectDataProvider directly in XAML.
You should instead think about implementing a separated presentation pattern such as Model-View-ViewModel to expose the result of your expression via a collection property on a ViewModel that you then bind as the DataContext of your view (Window).
Something like:
MainWindow.xaml
<Window x:Class="WpfApplication10.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">
<Grid>
<ItemsControl ItemsSource="{Binding Items}"/>
</Grid>
</Window>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.Win32;
namespace WpfApplication10 {
public class ViewModel {
public IEnumerable<String> Items {
get { return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(#"SOFTWARE\Vendor\Product\Systems").GetSubKeyNames(); }
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new ViewModel();
}
}
}