I am in process of developing a WPF app to practice Rx with MVVM.
Scenario
I have a View (MVVM) with a combo (some company name) and a detail (company journal) section; I wanted to populate detail section when user select an item from combo box.
The detail section data is populated with the help of a WCF service method, which take company name as input and Task<> as output.
Problem
Users sometime select combo box items in quick succession, which leads my window to freeze. I presume, this might be because of event queue up Or due to slow result from wcf service method.
Therefore, I am thinking to use Rx's FromEvent pattern (MVVM fashion), which shall be able to observe ComboBox SelectedItem Change event to load data from wcf and skip events those are coming in quick succession using some throttle.
I appreciate any sample implementations while respecting MVVM.
I think that the operator you are looking for is Switch(). I couldn't find an msdn page for it, but this is the signature you're after:
public static IObservable<TSource> Switch<TSource>(this IObservable<Task<TSource>> sources)
That will take an IObservable<Task<T>> and turn it into an IObservable<T> which produces the results of the most recent Task<T> received.
Here is an example implementation that doesn't use any MVVM, but I'm sure you can see how it would apply:
MainWindow.xaml
<Window x:Class="LastFromCombo.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>
<StackPanel>
<ComboBox Name="cbx" />
<TextBlock Name="result" />
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.cbx.ItemsSource = Enumerable.Range(0, 100);
Observable.FromEventPattern<SelectionChangedEventArgs>(this.cbx, "SelectionChanged")
.Select(ev => ev.EventArgs.AddedItems.Cast<object>().FirstOrDefault())
.Select(GetDetails)
.Switch()
.ObserveOnDispatcher()
.Subscribe(detail => this.result.Text = detail);
}
private static async Task<string> GetDetails(object data)
{
await Task.Delay(TimeSpan.FromSeconds(3.0));
return "Details from " + data;
}
}
Related
Not sure if I am just doing this wrong or am misunderstanding some of the examples already on stack overflow here and here.
I am trying to take a selected item from my first view model and pass it to another view model I am navigating to. The purpose of this is so I can display the item that has been passed and allow the user to work with it.
Passing from first view model
This is just a small snippet of the first view model. Here I am first navigating to the new page/view model. Then pass the SelectedRule object using a messenger. Navigation is done using a ViewModelLocator class / navigation service provided with MVVM Light.
private ApprovedBomRule _selectedRule = new ApprovedBomRule();
public ApprovedBomRule SelectedRule
{
get { return _selectedRule;}
set { Set(ref _selectedRule, value); }
}
private void NavigateToUpdateRule()
{
//Navigate to Update Rule page
_navigationService.NavigateTo("UpdateBomRuleView");
//Pass selected rule as a parameter using messenger service
ApprovedBomRule ruleToSend = SelectedRule; // Selected by user.
Messenger.Default.Send(ruleToSend);
}
On receiving view model
Here is my second view model where I register the same type of SelectedRule from above and set it to the public variable.
public class UpdateBomRuleViewModel : ViewModelBase
{
private ApprovedBomRule _passedRule;
public ApprovedBomRule PassedRule
{
get => _passedRule;
set => Set(ref _passedRule, value);
}
//Constructor
public UpdateBomRuleViewModel()
{
//Register message type
Messenger.Default.Register<ApprovedBomRule>(this, GetMessage);
}
//Set the property to passed object
public void GetMessage(ApprovedBomRule rule)
{
PassedRule = rule;
}
}
My constructor is reached and the register method is set, but the GetMessage() function is never called. What am I missing here?
EDIT
I narrowed down the problem to the fact that the register method is being called after the message is sent. Now the second problem I am running into is how do I have my second view model register before the send? I am using a viewmodel locator in my pages to determine the view models for each page. Even though I am doing the _navigation.NavigateTo() before sending the data, the view model is not initialized until after the send.
Example of viewmodel locator in page
<local:BasePage x:Class="YAI.BomConfigurator.Desktop.Views.Rules.UpdateBomRuleView"
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:YAI.BomConfigurator.Desktop"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="UpdateBomRuleView"
DataContext="{Binding UpdateBomRuleViewModel, Source={StaticResource Locator}}">
<Grid>
<TextBlock Text="{Binding PassedRule.Description}"
VerticalAlignment="Center"
HorizontalAlignment="Center">
</TextBlock>
</Grid>
Okay so I sort of found a solution to the problem. I used my ServiceLocator to get the instance before navigating.
var vm = ServiceLocator.Current.GetInstance<UpdateBomRuleViewModel>();
//Navigate to Update Rule page
_navigationService.NavigateTo("UpdateBomRuleView");
//Pass selected rule as a parameter using messenger service
ApprovedBomRule ruleToSend = SelectedRule; // Selected by user.
Messenger.Default.Send(ruleToSend);
This caused my register to be called before the send. I don't necessarily like this solution because the var vm is not being used for anything, but it works for now.
thank you for looking at the question.
You need to wait for the page show up before sending the message. It is weird that MVVMLight doesn't offer any NavigateAsync method like Prism, so you have to roll for your own.
await Application.Current.Dispatcher.Invoke(
() => _navigationService.NavigateTo("UpdateBomRuleView");
ApprovedBomRule ruleToSend = SelectedRule; // Selected by user.
Messenger.Default.Send(ruleToSend);
Slightly modified from my UWP code, but it should be fine for WPF.
I have a WPF application with two pages, now I wanted to navigate to the other page when the button in first the page is clicked (I wrote the command for button in the first page), but the logic should be through the viewmodel. How to achieve this?
When I write WPF applications that need to navigate to different pages, I like to follow Rachel Lim's method to implement it using DataTemplates and ViewModels. You can follow the link to her page to get the exact code for the solution, but I'll give a little summary of her method here.
In her method, she creates a ViewModel that represents the application and has a property called CurrentPage which holds a ViewModel. You can then create a command on the ApplicationViewModel called ChangePage. This command will take the ViewModel that is passed as a parameter and sets it to the CurrentPage.
The xaml takes the responsibility of switching out the correct views. When using this method, I put a ContentControl in my MainWindow and bind the Content property to ApplicationViewModel.CurrentPage. Then in the resources of the MainWindow, I create DataTemplates to tell the view "When I try to display this ViewModel, put that View on the screen".
You don't really provide any code. But I assume your Navigation is in your code behind. You could do this by binding a Command OneWayToSource.
XAML
<local:MainWindow x:Class="WpfNameSpace.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:WpfNameSpace"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
NavigatePageCommand="{Binding Path=MyViewModel.NavigateCommand, Mode=OneWayToSource}"
Title="MainWindow" Height="600" Width="800">
<Grid>
</Grid>
</local:MainWindow>
Please take a look at local:MainWindow.
C#
public partial class MainWindow : Window
{
public ICommand NavigatePageCommand
{
get { return (ICommand) GetValue(NavigatePageCommandProperty); }
set { SetValue(NavigatePageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigatePageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigatePageCommandProperty =
DependencyProperty.Register("NavigatePageCommand", typeof(ICommand), typeof(MainWindow),
new PropertyMetadata(0));
public MainWindow()
{
InitializeComponent();
NavigatePageCommand = new RelayCommand(Navigate);
}
public void Navigate()
{
//Do Navigation here
}
}
I assume you are familiar with Commands, ViewModels and Bindings and you get the idea.
I created a ViewModel and bound its property to two textboxes on UI. The value of the other textbox changes when I change the value of first and focus out of the textbox but I'm not implementing INotifyPropertyChanged. How is this working?
Following is XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Name}" />
<TextBox Text="{Binding Name}" />
</StackPanel>
</Window>
And following is my ViewModel
class ViewModel
{
public string Name { get; set; }
}
I tested it, you are right. Now i searched for it on the web, and found this.
Sorry to take so long to reply, actually you are encountering a another hidden aspect of WPF, that's it WPF's data binding engine will data bind to PropertyDescriptor instance which wraps the source property if the source object is a plain CLR object and doesn't implement INotifyPropertyChanged interface. And the data binding engine will try to subscribe to the property changed event through PropertyDescriptor.AddValueChanged() method. And when the target data bound element change the property values, data binding engine will call PropertyDescriptor.SetValue() method to transfer the changed value back to the source property, and it will simultaneously raise ValueChanged event to notify other subscribers (in this instance, the other subscribers will be the TextBlocks within the ListBox.
And if you are implementing INotifyPropertyChanged, you are fully responsible to implement the change notification in every setter of the properties which needs to be data bound to the UI. Otherwise, the change will be not synchronized as you'd expect.
Hope this clears things up a little bit.
So basically you can do this, as long as its a plain CLR object. Pretty neat but totally unexpected - and i have done a bit of WPF work the past years. You never stop learning new things, right?
As suggested by Hasan Khan, here is another link to a pretty interesting article on this subject.
Note this only works when using binding. If you update the values from code, the change won't be notified. [...]
WPF uses the much lighter weight PropertyInfo class when binding. If you explicitly implement INotifyPropertyChanged, all WPF needs to do is call the PropertyInfo.GetValue method to get the latest value. That's quite a bit less work than getting all the descriptors. Descriptors end up costing in the order of 4x the memory of the property info classes. [...]
Implementing INotifyPropertyChanged can be a fair bit of tedious development work. However, you'll need to weigh that work against the runtime footprint (memory and CPU) of your WPF application. Implementing INPC yourself will save runtime CPU and memory.
Edit:
Updating this, since i still get comments and upvotes now and then from here, so it clearly is still relevant, even thouh i myself have not worked with WPF for quite some time now. However, as mentioned in the comments, be aware that this may cause memory leaks. Its also supposedly heavy on the Reflection usage, which has been mentioned as well.
I just found out that this also works in WinForms, kinda :/
public class Test
{
public bool IsEnabled { get; set; }
}
var test = new Test();
var btn = new Button { Enabled = false, Text = "Button" };
var binding = new Binding("Enabled", test, "IsEnabled");
btn.DataBindings.Add(binding);
var frm = new Form();
frm.Controls.Add(btn);
test.IsEnabled = true;
Application.Run(frm);
Strangely though, this doesn't disable the button:
btn.Enabled = false;
This does:
test.IsEnabled = false;
I can explain why the property is updated when focus changes: all Bindings have an UpdateSourceTrigger property which indicates when the source property will be updated. The default value for this is defined on each DependencyProperty and for the TextBox.Text property is set to LostFocus, meaning that the property will be updated when the control loses focus.
I believe UrbanEsc's answer explains why the value is updated at all
To Reproduce my case (.net 4.0)
Create a WPF Application (MainWindow.xaml)
Add a Winform user control that contains a textbox (UserConrol1.cs - Winform)
Put UserControl1 into MainWindow.xaml with windowsformshost
Add another WPF Window that contains a textbox(wpf) to project (Window1.xaml)
Create and Show Window1 after MainWindow InitializeComponent
Your project is ready,
Run Project and set textbox focused in MainWindow.xaml (that in WindowsFormsHost)
Deactivate your application by opening a window (Windows file explorer ,notepad, winamp etc.)
Try to write in textbox that in Window1 window by clicking textbox with mouse
And you will see that you can't set focus on textbox in Window1 because MainWindow Texbox( in winformshost will steal your focus on you application got activating)
Any idea?
MainWindow.xaml
<Window x:Class="WinFormsHostFocusProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WinFormsHostFocusProblem"
xmlns:my="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
Title="MainWindow" Height="350" Width="525">
<Grid>
<my:WindowsFormsHost Focusable="False" >
<local:UserControl1>
</local:UserControl1>
</my:WindowsFormsHost>
</Grid>
</Window>
MainWindow.xaml.cs
namespace WinFormsHostFocusProblem
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Window1 window1 = new Window1();
window1.Show();
}
}
}
Window1.xaml
<Window x:Class="WinFormsHostFocusProblem.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WinFormsHostFocusProblem"
xmlns:my="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
Topmost="True"
Title="Window1" Height="300" Width="300" Background="Red">
<Grid>
<TextBox Height="25">asd</TextBox>
</Grid>
</Window>
Window1.xaml.cs
namespace WinFormsHostFocusProblem
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
}
I used my MSDN support contract to get an answer to this problem. The engineer was able to repro from yunusayd's sample and confirmed it is almost certainly a bug in WindowsFormsHost.
Thanks to yunus for the minimal repro sample and Keith at Microsoft for tackling the issue and providing a workaround in less than one day.
Workaround code follows. It works by using .NET reflection to change a private variable used in WindowsFormsHost and disable the trigger for the bug. According to the engineer I worked with, this relies on WPF internals, but he spoke with product team members and it should be safe to use. There's no guarantee of lack of side effects, of course, but so far I haven't found any problems in my testing with multiple WindowsFormsHosts in multiple WPF windows (maybe nesting would be trickier). I modified the original workaround to work generically with multiple windows. You can just as easily hardcode references to specific windows and named WindowsFormsHost controls in the Application_Deactivated event and skip the whole "LastActive" scheme and extension methods.
// App.xaml.cs: you must hook up to Application.Deactivated
void Application_Deactivated(object sender, EventArgs e)
{
foreach (Window w in windows)
{
foreach (var host in UI.DependencyObjectExtension.AllLogicalChildren(w).
Where(c => c is WindowsFormsHost))
{
FIELD_FOCUSED_CHILD.SetValue(host, null);
}
}
}
public readonly static FieldInfo FIELD_FOCUSED_CHILD = typeof(System.Windows.Forms.Integration.WindowsFormsHost).
GetField("_focusedChild", BindingFlags.NonPublic | BindingFlags.Instance);
public static class DependencyObjectExtension
{
/// <summary>
/// Returns a collection of o's logical children, recursively.
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
public static IEnumerable<DependencyObject> AllLogicalChildren(this DependencyObject o)
{
foreach (var child in LogicalTreeHelper.GetChildren(o))
{
if (child is DependencyObject)
{
yield return (DependencyObject)child;
if (child is DependencyObject)
{
foreach (var innerChild in AllLogicalChildren((DependencyObject)child))
{
yield return innerChild;
}
}
}
}
}
}
We had a similar problem in one of our applications and found that upgrading to .net 4.5 seems to have fixed a good portion of our application's WPF/WinForms focus issues including a similar one to this.
In addition, the _focusedChild field no longer exists in the .net 4.5 version of WindowsFormsHost
I'm writing an application in WPF, using the MVVm toolkit and have problems with hooking up the viewmodel and view.
The model is created with ado.net entity framework.
The viewmodel:
public class CustomerViewModel
{
private Models.Customer customer;
//constructor
private ObservableCollection<Models.Customer> _customer = new ObservableCollection<Models.Customer>();
public ObservableCollection<Models.Customer> AllCustomers
{
get { return _customer; }
}
private Models.Customer _selectedItem;
public Models.Customer SelectedItem
{
get { return _selectedItem; }
}
public void LoadCustomers()
{
List<Models.Customer> list = DataAccessLayer.getcustomers();
foreach (Models.Customer customer in list)
{
this._customer.Add(customer);
}
}
}
And the view (no code behind at the moment):
<UserControl x:Class="Customers.Customer"
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"
xmlns:vm ="clr-namespace:Customers.ViewModels"
d:DesignHeight="300" d:DesignWidth="300"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit" >
<Grid>
<toolkit:DataGrid ItemsSource="{Binding AllCustomers}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" AutoGenerateColumns="True">
</toolkit:DataGrid>
<toolkit:DataGrid ItemsSource="{Binding SelectedItem.Orders}">
</toolkit:DataGrid>
</Grid>
</UserControl>
And dataaccesslayer class:
class DataAccessLayer
{
public List<Customer> customers = new List<Customer>();
public static List<Customer> getcustomers()
{
entities db = new entities();
var customers = from c in db.Customer.Include("Orders")
select c;
return customers.ToList();
}
}
The problem is that no data is displayed simply because the data context is not set. I tried to do it in a code-behind but is did not work. I would prefer to do it in a xaml file anyway. Another problem is with the SelectedItem binding - the code is never used.
Since this is using the MVVM paradigm, I would instance your ViewModel in the constructor for the View. My View/ViewModels typically follow this sequence of events:
View is instanced
View constructor instances ViewModel
ViewModel initializes
ViewModel runs data getting procedures(separate thread)
ViewModel calls OnPropertyChanged("") to alert View that something has changed; check everything
My ViewModel is instanced from the XAML codebehind (sorry this is in VB.NET, have not gotten around to learning C# well enough to trust myself with it):
Public Sub New()
MyBase.New()
Me.DataContext = New EditShipmentViewModel(Me) 'pass the view in to set as a View variable
Me.InitializeComponent()
End Sub
Initially I hoped to have something like
<UserControl>
<UserControl.DataContext>
<Local:EditShipmentViewModel>
</UserControl.DataContext>
</UserControl>
But that did not work out like I wanted it to.
Hope that helps a little.
Is this what you're looking for?
I set my viewmodel datacontext the same way I observed Blend4 to. That is, if my viewmodel is called MainViewModel, I reference it in the view like:
<UserControl.Resources>
<local:MainViewModel x:Key="MainViewModelDataSource" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource MainViewModelDataSource}}">
...view xaml stuff
</Grid>
also, if you're loading data from a database in the constructor of your viewmodel, don't forget to add a helper method around it like:
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
myCollection = new ObservableCollection<Blah>(something);
}
so that visual studio/Blend4 doesn't crash trying to retrieve the data from the database connection in the Designer. I personally load data in the constructor quite often, just because I need it right away, and for it to be cached in memory from startup.
:)