I got a Silverlight page opening a dialog. It is an administration page with some advanced logic talking to a database over a webservice. For the Silverlight client side, we use MVVM to its fullest. Opening the dialog the first time, everything works fine according to the implemented logic.
The problem: The second time opening the dialog, our data bound setters start receiving the wrong values.
Code:
How the dialog is created:
MyPopupViewModel myPopup = new MyPopupViewModel();
Caliburn.Micro.Execute.OnUIThread(() => WindowManager.ShowDialog(myPopup));
One of the bindings that eventually get the wrong values:
<ComboBox
ItemsSource="{Binding YesNoItems}"
SelectedValue="{Binding IsSynchronizing, Mode=TwoWay, Converter={StaticResource BooleanToYesNoConverter}}"
/>
What I have tried:
I put breakpoints in the setters. This is how I realized the logic is correct but that the setter is called with other values the second time it is opened.
I found the getters being called {1, 2, 1} times the {1st, 2nd, 3rd} time they are opened.
I made sure the dialog is initialized each time. Maybe Caliburn-micro caches the old one somehow, but it apparently does not cache it for long, as it works fine again the third time it is opened.
Solved it by adding the following decorator in the constructor of the dialog View, in the code-behind. I already had that decorator in the ViewModel, but the View apparently needed it too.
PartCreationPolicy(CreationPolicy.NonShared)]
A colleague helped me to find this out by breakpointing and then setting an ID for each instance. That way we proved there were several instances. Very practical. So the Views needed to be told to recreate each time instead of being reused.
The final code-behind looks like this:
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class MyPopupView
{
public MyPopupView()
{
InitializeComponent();
}
}
Related
I'm working on a WPF MVVM application. I'm showing some data in a datagrid. I've two buttons to Add and Edit the selected record. I've data in ViewModel and I've to show another window (view) and make sure that ViewModels should have no information about views.
Where should I create its view and viewmodel?
How to get the data back and update datagrid?
How can I achieve this in MVVM?
We have not yet decided to use any framework, so I've to create my own interface.
Note: This ended up being quite a long answer - please ask me if anything is unclear
The implementation of dialog windows is a contentious issue in MVVM designs, and different people use different approaches.
Like you, I've decided not to use any framework and implement most things by hand. When it comes to dialog windows, I choose to be pragmatic about my implementation of MVVM, by launching the Dialog Window from inside my ViewModel. Also, I allow each Dialog ViewModel to have a reference to the Window it is displayed in, so it can close it when appropriate (details below). This breaks some of the strict MVVM "rules", but it gets the job done.
The main downside of this is that it might break unit testing if you are testing something that goes through a dialog. However, you can go a long way without running into that problem and it has not bothered me yet.
I've built up a bit of a library of dialog ViewModels which I can easily extend. It's way too much code to post here, but I'll show you the highlights.
Base ViewModel for Dialogs
Each of my dialog windows has a ViewModel that inherits from DialogViewModelBase, which is similiar to my regular ViewModelBase in that it provides support for INotifyPropertyChanged etc. The interesting part is this public method, which I call from wherever to launch the Dialog:
/// <summary>
/// Creates window instance for this dialog viewmodel and displays it, getting the dialog result.
/// </summary>
public void ShowDialogWindow()
{
// This is a property of the DialogViewModelBase class - thus, each DialogViewModel holds a reference to its own DialogWindow:
this.DialogWindow = new Dialogs.Views.DialogWindow();
// Tell the DialogWindow to display this ViewModel:
this.DialogWindow.DataContext = this;
// Launch the Window, using a method of the Window baseclass, that only returns when the window is closed:
this.DialogWindow.ShowDialog();
}
Window launched in the above method will close when its Window.DialogResult property is set. This is why the DialogWindow is a property of the DialogViewModelBase class - when the subclassing dialog ViewModel wants to close the dialog window, it simply sets the result:
protected void CloseDialogWithResult(bool dialogWindowResult)
{
// Setting this property automatically closes the dialog window:
this.DialogWindow.DialogResult = dialogWindowResult;
}
Host Window for Dialog Views
The Dialogs.Views.DialogWindow class that the ShowDialogWindow method instantiates is defined in XAML and is a subclass of Window. It has two important features. The first is that it's primary content element is simply a ContentControl that binds to the current context. This allows me to define different Views for different subclasses of DialogViewModelBase, and the DialogWindow will host the corresponding View based on the type of the context:
<ContentControl Content="{Binding}" /> <!-- In reality this is inside a border etc but its simplified here for demonstration -->
The second important feature of the DialogWindow XAML is that it defines which dialog Views go with which dialog ViewModels. Here is a sample:
<Window.Resources>
<!-- DEFAULT ViewModel-View TEMPLATES -->
<DataTemplate DataType="{x:Type dialogs:YesNoMessageBoxDialogViewModel}">
<views:MessageBoxView />
</DataTemplate>
<DataTemplate DataType="{x:Type dialogs:ErrorDialogViewModel}">
<views:ErrorDialogView/>
</DataTemplate>
</Window.Resources>
What all this does, is that I can define dialogs as subclasses to DialogViewModelBase and implement a View for each, and then tell DialogWindow which View its ContentControl must show for which dialog ViewModel.
Launching a Dialog and getting results
Below is a sample from one of my application ViewModels, in which I launch a Dialog Window that allows the user to select an Asset Type for creation:
public void CreateNewAsset()
{
// Instantiate desired Dialog ViewModel:
Dialogs.NewAssetTypeSelectionDialogViewModel dialog = new Dialogs.NewAssetTypeSelectionDialogViewModel();
// Launch Dialog by calling method on Dialog base class:
dialog.ShowDialogWindow();
// Execution will halt here until the Dialog window closes...
// The user's selection is stored in a property on the dialog ViewModel, and can now be retrieved:
CalculatorBase.AssetTypeEnum newAssetType = dialog.AssetType;
switch (newAssetType)
{
// Do stuff based on user's selection...
}
}
PS: I should really write a blog entry about this - when I do, I will post the link here, as the blog entry will probably have more complete code samples.
It depends how you are handling the data. I will assume that changes made in the popup window can be accepted only when user clicks something like save in other case they should be discarded.
So firstly, I would suggest using MVC approach as controller is perfect for such tasks. You build viewmodels in it, assign them o views and show the views. VM's simply keeps data and commands, commands execute methods are kept in controller. In other words you have singleton class which manages your VM's and views.
You should check out Prism framework. It offers great things like view regios where you can inject different user controls on the runtime, commanding and MVC layering out of the box alongside IOC and DI patterns.
I am relatively new to MVVM, and I am trying to code up a basic Status Bar for an MVVM WPF application. I think I have the gist of things, but for some reason, the status bar does not always update, and I am not sure why.
In my ViewModel, I have a basic property that I update when I change a status message:
public string StatusMessage
{
get { return _statusMessage; }
set
{
if (value == _statusMessage) return;
_statusMessage = value;
base.OnPropertyChanged(() => this.StatusMessage);
}
}
My OnPropertyChanged method (which I have in a base ViewModel class that implements INotifyPropertyChanged) looks like so (got this idea from Gunther Foidl; wish I could claim credit for it because I think it's slick but I'm not quite that smart):
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> exp)
{
MemberExpression me = exp.Body as MemberExpression;
string propName = me.Member.Name;
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
At any rate, this all works great for all of my controls except one. On my MainWindow.xaml file, I have a StatusBarItem control bound to the above property, like so (the rest of the XAML has been trimmed for space reasons):
<StatusBarItem Grid.Column="0">
<TextBlock TextTrimming="CharacterEllipsis" Text="{Binding Path=StatusMessage}" />
</StatusBarItem>
When I run my application (which hits a couple of DBs in addition to generating a document from template and a bunch of other fairly resource-intensive stuff), some, but not all, messages show up on the status bar. I have debugged and verified that the messages all make it into the StatusMessage property, above (and the ensuing private variable), they just don't seem to be refreshing in the UI.
I have looked at several examples that use BackgroundWorker instances for ProgressBar controls, but haven't seen any for StatusBarItem controls, and am not really sure how to translate one to the other.
I have also used Tasks before in previous C# 4.0 and WPF apps, and figure it's probably a good way to go, but I haven't really been able to figure out how/where to designate the UI task (I've always done it in the code-behind for the MainWindow before, but I'm striving for a zero-code-behind to stay in keeping with MVVM here).
I'm pretty sure that a multi-threaded approach is the way to go; I just don't know enough about one approach (I know a little bit of this and a little bit of that) to make it work. I did see a couple of posts that used the older threading approach directly, but I pretty much stayed away from multithreading programming until I started using Tasks with .NET 4.0 (finding them a little easier to comprehend and keep track of), so I had a bit of trouble making sense of them.
Can anyone take pity on me and point me in the right direction, or suggest further debugging I can do? Thanks!
1)Reflection based binding can be source of error sometimes because of inlining. Try to see what happens if you notifypropertychanged with simple string instead of reflection.
2) if you are using multi threads there maybe a chance that you setup StatusMessage not from UIThread in that case it won't be able to update UI, you could invoke setter code on UI Dispatcher to see if that helps
3) check whether binding works , in constructor of xaml form modify StatusMessage directly on VM and see whether the change is shown on UI without invoking multithreaded service calls which introduce additional variables to simple textblock - string binding
4) if that doesn't help you could create a simple xaml form with single textblock bind it to your big viewmodel and see what happens, if nothing works you can begin cutting VM class to make it simpler so binding eventually starts to work and you find an error
5) if you think that statusbar is the problem see if single textblock without statusbar (extract xaml part from your example) works
Somewhere the notification does not get through.
I would try :
Add a dummy valueconverter on the textbinding so you can set a breakpoint and see if you are called
Dispatching the property set to set the value at a "better" time - that is sometimes nessesary.
Dispatching the set might do the trick.
Look at this:
<ItemsControl ItemsSource="{x:Static local:Cache.Colors}" />
This binds the ItemsControl to a static property called List. In this case, the Colors property is part of a class called Cache.
But there is a problem. When you bind in this way, the Colors property is called during the Initialize method, prior to when Security is established in the application.
Because Security has not been established, then calling Colors results in an exception as Security is a requirement for successfully calling the data service.
The solution moves this from XAML to code behind and ensures it is executed in the Loaded event instead of in the constructor during Initialize.
The real problem here is, I would like to do this in XAML. Is it possible?
I have typically solved this by having the ItemsSource being bound to implement the INotifyCollectionChanged interface. At initialization the items source would be empty, and then at load time the items source is populated. The population of the items source raises the collection changed event, causing your items control to rebind/add the new items in the source.
My solution was to run it in the App.xaml.cs before anything else.
I'm using PRISM in a SilverLight 4 application. I have a problem where views registered to some regions doesn't get displayed.
When loading a module at startup I register some views to regions as follows:
RegionManager.RegisterViewWithRegion("MyRegion1", typeof(IMySubView1));
RegionManager.RegisterViewWithRegion("MyRegion2", typeof(IMySubView2));
I have a view implementing an interface called IMyView, where the xaml have two contentcontrols with regions defined in a grid like this:
<ContentControl Regions:RegionManager.RegionName="MyRegion1" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Grid.Row="0" Grid.RowSpan="1"/>
<ContentControl Regions:RegionManager.RegionName="MyRegion2" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Grid.Row="1" Grid.RowSpan="1"/>
I have tried two different methods for adding the view to the main region. Both adds the view and basic elements such as buttons get displayed, but the regions defined in the view does not get filled with associated views.
Method 1:
object obj = _container.Resolve<IMyView>();
IRegion mainRegion = _regionManager.Regions["MainViewRegion"];
IRegionManager scoped = mainRegion.Add(obj, "test", true);
mainRegion.Activate(obj);
// Enabling the following call, it will fail saying the region MyRegion1 does not exist. Feels like it should?
// IRegion myRegion = scoped.Regions["MyRegion1"];
Method 2:
object obj = _container.Resolve<IMyView>();
_regionManager.AddToRegion("MainViewRegion", obj);
_regionManager.Regions["MainViewRegion"].Activate(obj);
It feels like the regions defined in the xaml file doesn't get registered, and because of that the registered views do not get displayed.
The MainViewRegion is defined in the shell in a TabControl as this:
<TabControl Margin="8,0,8,8" Regions:RegionManager.RegionName="MainViewRegion">
Any suggestions on solving my problem will be greatly appreciated!
I'm facing the same problem. The idea is that for whatever reason view injection {mainRegion.Add(obj, "test", true)} for already created regions doesn't show the view. A workaround that worked for me, is to create the region from code, and then inject the view. Something like this:
Microsoft.Practices.Composite.Presentation.Regions.RegionManager.SetRegionManager(headerRegionContainer, _RegionManager);
Microsoft.Practices.Composite.Presentation.Regions.RegionManager.SetRegionName(headerRegionContainer, regionName);
var view = _UnityContainer.Resolve(bag.HeaderViewType);
_RegionManager.Regions[regionName].Add(view);
_RegionManager.Regions[regionName].Activate(view);
Unfortunately for me I can't reach my goal this way, but maybe you can.
Regards,
Levente
After many hours of troubleshooting I found something.
In Composite.Presentation\Regions\RegionManager.cs there is a method called IsInDesignMode. When a region is about to be created this method is called, and if this method returns true the region is not created. See below:
private static void OnSetRegionNameCallback(DependencyObject element, DependencyPropertyChangedEventArgs args)
{
if (!IsInDesignMode(element))
{
CreateRegion(element);
}
}
private static bool IsInDesignMode(DependencyObject element)
{
// Due to a known issue in Cider, GetIsInDesignMode attached property value is not enough to know if it's in design mode.
return DesignerProperties.GetIsInDesignMode(element) || Application.Current == null
|| Application.Current.GetType() == typeof(Application);
}
When our silverlight application starts up and the regions in the shell gets created everything is fine, the Application.Current property is of type "MyName.Shell.App". But when a view gets added after startup, as a response to user input, the Application.Current type is suddenly of type "Application", and thus the IsInDesignMode method returns true and the regions are not created.
If I remove the Application.Current conditions everything works as expected. So the question is, is there something wrong in my application or is there something wrong in the prism source code?
where is your _regionManager coming from ?
Did you write a proper BootStrapper
?
You need to write a class inheriting from MefBootstrapper or UnityBootstrapper (or a custom one if you're not using neither of those IoC/Extension framework) in order to register all your needed types within the IoC container.
could you post the BootStrapper code ?
The problem is gone in Prism version 4.0, I was running Prism version 2.2 when the problem occured.
I'm tryihg to bind a combobox item source to a static resource. I'm oversimplfying my example so that it's easy to understand what i'm doing.
So I have created a class
public class A : ObservableCollection<string>
{
public A()
{
IKBDomainContext Context = new IKBDomainContext();
Context.Load(Context.GetIBOptionsQuery("2C6C1Q"), p =>
{
foreach (var item in SkinContext.IKBOptions)
{
this.Add(item);
}
}, null);
}
}
So the class has a constructor that populates itself using a domaincontext that gets data from a persisted database. I'm only doing reads on this list so dont have to worry about persisting back.
in xaml i add a reference to the namespace of this class then I add it as a usercontrol.resources to the page control.
<UserControl.Resources>
<This:A x:Key="A"/>
</UserControl.Resources>
and then i use it this staticresource to bind it to my combobox items source.in reality i have to use a datatemplate to display this object properly but i wont add that here.
<Combobox ItemsSource="{StaticResource A}"/>
Now when I'm in the designer I get the error:
Cannot Create an Instance of "A".
If i compile and run the code, it runs just fine. This seems to only affect the editing of the xaml page.
What am I doing wrong?
When running in the designer the full application runtime is not available. However the designer doesn't just magically know how to mock the UI of a UserControl. Its Xaml is parsed and the objects described there are instantiated.
Its up to you to code your classes to cope with existence in a designer. You can use the static proeprty DesignerProperties.IsInDesignTool to determine if your code is currently being used in a designer tool or not.
If in a designer you could deliver a set of test data rather than attempt to access a data service.
My problem is same as described above and i alse used DesignerProperties.IsInDesignTool
but i can't open usercontrol in visual studio for designing purpose