|
Jean-Louis Leroy wrote: I want to support the New/Open/Save/SaveAs cycle for Reports [...] But how do I implement SaveAs ? By the time I know the end user wants to make a new object, the old one has already been messed up.
Thinking about it, one solution out of this problem is to drop the SaveAs command (posterior to changes) and replace it with a Duplicate command (prior changes). Like in Mac OS X Lion
Then what would Save mean ? Worse, what would it mean if a user changes the data, then never executes Save ?
It seems to me that MVVM may end up curbing the way we design UIs. Another example, it discourages modal dialogs, or at least it makes them difficult to implement.
I wonder if it's legitimate for a code pattern to have such impact on the end-user experience. Isn't it the tail that wags the dog ?
edit: grammar
|
|
|
|
|
Jean-Louis Leroy wrote: it discourages modal dialogs, or at least it makes them difficult to
implement.
Why do you think it makes modal dialogs difficult to implement? I've never found that to be the case.
|
|
|
|
|
Pete O'Hanlon wrote: Why do you think it makes modal dialogs difficult to implement? I've never found that to be the case.
I suppose that many get their first exposure to MVVM via Josh Smith's article. But his sample app doesn't contain any modal dialogs. However, it's still one of the first things you need when implementing a real app. So when you turn to Google and search for "MVVM modal dialog" you get flooded with solutions falling roughly in five families: use a control; use a service; overlay a control on your view and disable its controls; what the heck, a bit of code-behind won't hurt; and finally, don't do modal, it's so nineties.
It seems that no consensus has emerged on the issue yet (or I've missed it). Not that I care that much about consensus anyway, but, by design or by accident, it's a difficulty for newbies...
What solution do you use ?
|
|
|
|
|
Over time, I've really settled into using one of two methods (primarily because I contributed to the solutions on them). I originally used the features of Onyx[^] to do the dialogs, but I then moved to using MefedMVVM[^].
I use a modal dialog service that I grab a hold of in my particular VM, and use that to instantiate the dialog. The trick is to use an interface to represent what you want from the dialog, and inject a concrete implementation of this at runtime. It's all very testable, and easy to support.
|
|
|
|
|
I don't see why people have problems with MVVM modal dialogs. There are many times when a modal dialog is perfectly appropriate. Yes, there are many, many different opinions on how they should be done. There is really only an issue if you plan to unit test your VM. The service solution usually works quite well and keeps the testability.
|
|
|
|
|
I agree the service approach is the way I do it, and I love it, works great.
Sacha Barber
- Microsoft Visual C# MVP 2008-2011
- Codeproject MVP 2008-2011
Open Source ProjectsCinch SL/WPF MVVM
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
My Blog : sachabarber.net
|
|
|
|
|
Jean-Louis Leroy wrote: Here I stumble upon a difficulty. Consider a Person that has a birth date stored
in a DateTime. Obviously we don't want to present that in the VM. Instead
we want to map the double Person.BirthDate to a text input field in
the view - or three text boxes for day, month and year, or some Calendar
control. So what's the VM between View and Model going to look like ? Does it
contain one (or three) string properties ?
If you have a date and/or time, I would use a date and/or time picker and keep the value as a DateTime. Having 3 (or more) text boxes throws out a ton of functionality that you would have for free and kind of makes your app look "low budget" IMO.
I do want to touch on something I said in my original response... IMO, the model should implement INPC so you CAN bind to properties in it directly. Imagine if you have 50 properties, it doesn't really make sense to wrap 50 properties just for the sake of wrapping them. Thats just a practice in typing. Unfortunately, thats not always possible. Web service proxies do not implement INPC, neither does native code, etc. There is absolutely nothing wrong with your VM exposing a Person property and your view binding to "Person.BirthDate".
Jean-Louis Leroy wrote: Or does it ? Back to my Person.BirthDate example, suppose that I
have VM1 that exposes the birth date as a single string property (BirthDate) in
yyyy/mm/dd format. When the View sets the VM's BirthDate property the
Model.BirthDate is updated. Now VM2 uses three string properties:
Day, Month and Year. But changing the birth date via VM1 fires a "BirthDate"
event that is significant only to the View that displays VM1. The change
notifications are sent as changes to the VM's properties, whereas (if
memory serves) in Smalltalk MVC they are sent as updates to the Model's
properties.
A properly implemented model should handle this case. Updating Day, month, year should send an INPC for Day (or month or year) AND Date. Updating the Date property should send an INPC for day, month, year and Date.
|
|
|
|
|
Errm. Are you 100% sure about this design approach as an absolute? How do you handle validation in this case or support for IEditableObject? The reality is, a hybrid approach will generally be necessary.
In the case where you want to simply forward a property on, then your approach is fine, but where you need to validate something before it can be committed, then you should use an intermediary value. Similarly, if changes can be rolled back then you should support an intermediary value. That's the approach to use.
Take the case you mentioned - where you have a huge amount of text in a model, the VM would generally do some clever processing so that only a subset of the text was kept bound to the view, virtualized in other words, and the model would be updated with the subset. In this way, the VM simply presents a snapshot of some of the text at that point in time.
|
|
|
|
|
The model should often times encapsulate all this functionality so it can be reused. Validation isn't an issue. You can handle that in the setter, or more appropriately have the model implement IDataErrorInfo.
|
|
|
|
|
Basically, with this approach, you have virtually completely defeated the whole purpose of MVVM. The point of putting this into the VM is that it is appropriate for the View and the ViewModel to interact, but you are delegating responsibility to the Model. What's the point of the VM anymore with this - it's purely acting to relay data in your approach. It's no longer adding any value.
|
|
|
|
|
I get your point, and agree to a certain extent, but also disagree to a certain extent .
Do you agree that the model should implement INPC? or do you believe that duty falls to the VM and that the model should just be a simple POCO? I think thats the models job. Why duplicate INPC code in every single VM that uses the model? If the model is already implementing INPC (and INCC), how is that any different from it implementing IEditableObject and IDataErrorInfo?
I am not saying the model should handle everything, it most certainly should not. It should however encapsulate everything to do with an object so the code is centralized and not C&P'ed everywhere.
The VM still has plenty to do: commands, occasional repacking of data, exposing non-model related properties, etc. The model doesn't really expose anything GUI related, thats the VMs job.
|
|
|
|
|
That's fair enough. We do agree here then. My opinion was not that the VM should reimplement everything the model can do, just that there were certain occasions when it has to do extra work so you have to take it on a case by case basis.
|
|
|
|
|
I don't think you have a misunderstanding either, FWIW.
There are different ways of doing things - and no one way is intrinsically 'the right way'
I tend to get my Model object, use it to create a ViewData object (which I use inside the ViewModel) and don't persist the Model object.
I use a specific user-controlled option to update my repository - which creates a new Model from the ViewData
|
|
|
|
|
Hmm.
I would say that your business level entities coming from your data layer have no business at all in your GUI layer.
It seems as if you do not have a layered application and that is your first problem.
At the minimum, I'ld say a GUI layer, business layer and data layer is essential.
Your mvvm pattern is found in your GUI layer and deals exclusively with GUI stuff.
Use dto's to send data from and to your business.
Your project will look like a bowl of spaghetti if you don't structure it properly.
|
|
|
|
|
I guess people are relucent to publish low-brow MVVM examples in fear of getting shot down in flames.
The way I do it (using Entity Framwork) is the ViewModel provides a SaveCommand which the View binds to. After all, it should be up to the user to determine if the data is saved. So when the user clicks the Save button or other events occur, the command is fired and a method can handle the logic to persist the changes back to model.
In EF it's a one liner myContext.SaveChanges();
In your situation it sounds like you'll need to do a bit more leg work. But the stategy should still work. You need to catch the event in the ViewModel using a command.
"You get that on the big jobs."
|
|
|
|
|
I take option 2 over option 1 'almost always'
I treat my Model object as an object whose job it is to go get the data for the client. Once delivered its job is done, so I don't keep a reference to the Model in my ViewModel (again, 'usually' )
So I rarely Update model objects - as they're going to be persisted back in the DB - I create new objects when required and send them back to be stored.
I must confess to not quite understanding your scenario here - how your report and Parameter objects interract - but assuming you are maintaining the report and/or parameter objects, then I would normally have some sort of Apply method as per your option2.
if you do it this way, then at least you have the option of using the Apply method as frequently or infrequently as you want to - on a timer, every time a parameter type is changed - whatever.
But I don't think there's a 'right' way of doing it - and it depends on your exact requirements.
One of the downsides of option 1 is the ability to revert changes quickly... As you're maintaining a reference to the Model and want to cancel changes, you can just re-set your VM from the Model and you're back!
|
|
|
|
|
How do we change themes dynamically, if themes are stored into a DLL?
Generally, if theme is in form of .xaml file, we write in xaml
<ResourceDictionary Source="Themes/ShinyRed.xaml"/>
And in CS:
ResourceDictionary skin = new ResourceDictionary();
skin.Source = new Uri(@"Themes/ShinyRed.xaml", UriKind.Relative);
Window win = Window.GetWindow(this);
win.Resources.MergedDictionaries.Clear();
win.Resources.MergedDictionaries.Add(skin);
where 'Themes' is folder name and 'ShinyRed.xaml' is theme name.
Now, if theme is in form of DLL file, we write in xaml
<ResourceDictionary Source="/ThemeDll;component/ShinyBlue.xaml"/>
where 'ThemeDll' is the name of DLL, and ShinyBlue.xaml is theme name.
How to get same result at run-time?
|
|
|
|
|
I think you can get the result same way as you do it with normal xaml file
in CS:
ResourceDictionary skin = new ResourceDictionary();
Instead of
skin.Source = new Uri(@"Themes/ShinyRed.xaml", UriKind.Relative);
Use this
skin.Source = new Uri(@"/ThemeDll;component/ShinyBlue.xaml", UriKind.Relative);
Window win = Window.GetWindow(this);
win.Resources.MergedDictionaries.Clear();
win.Resources.MergedDictionaries.Add(skin);
it works fine for me.
I hope i got your question correctly.
Work relieves us from three great evils, boredom, vice, and want.
|
|
|
|
|
Thanks!
|
|
|
|
|
Well, I have been trying to figure this one out for three days straight and I still haven't come up with a fix.
Basically I am trying to swap out the clicked Ellipse with the only empty spot on the 3x3 checkerboard. 8 of the 9 squares are occupied. I need to find the one spot that is not occupied and I can't seem to do it. Why? Because even though there is an empty spot on the grid at runtime, Javascript refuses to acknowledge this. I used the line: var childrenCount = canvasArray[i].children.count; .. so that's all the canvases. If at runtime there is an empty spot, then how come my code refuses to see it? Or am I not writing the right code? How is the empty spot represented and found at runtime? That's what I want to know.
Here is the pseudocode:
if (squareOnGrid is empty) {
log.write(squareOnGrid + ' is empty');
emptySquare = squareOnGrid;
oldPositionBorder = sender;
oldPositionR = checkerPiece.row;
oldPositionC = checkerPiece.col;
checkerPiece.row = empty.row;
checkerPiece.column = squareOnGrid.column;
oldPositionBorder = null;
}
I want to do this with Javascript (not C#).
I already have this (Javascript):
<br />
function switchPlaces(sender) {<br />
<br />
for (var i = 0; i < canvasArray.length; i++) {<br />
var oldLocationBorderParent = sender;<br />
var oldLocationCanvasParent = oldLocationBorderParent.findName('canvas' + (i + 1));<br />
var oldLocationChild = oldLocationCanvasParent.findName('ellipse' + (i + 1));<br />
<br />
var childrenCount = canvasArray[i].children.count;<br />
log.info(childrenCount);
<br />
if (childrenCount == 0) {<br />
log.info(canvasArray[i] + ' has no children');<br />
var emptySpot = canvasArray[i];<br />
sender['Grid.Row'] = emptySpot['Grid.Row'];<br />
sender['Grid.Column'] = emptySpot['Grid.Column'];<br />
oldLocationCanvasParent.children.remove(oldLocationChild);<br />
}<br />
}<br />
}<br />
Here is my Silverlight code:
<br />
<Grid<br />
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br />
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br />
Loaded="onLoaded" ShowGridLines="True" Background="CornflowerBlue"><br />
<br />
<Grid.ColumnDefinitions><br />
<ColumnDefinition Width="100"/><br />
<ColumnDefinition Width="100"/><br />
<ColumnDefinition Width="100"/><br />
<ColumnDefinition/><br />
</Grid.ColumnDefinitions><br />
<br />
<Grid.RowDefinitions><br />
<RowDefinition Height="100"/><br />
<RowDefinition Height="100"/><br />
<RowDefinition Height="100"/><br />
</Grid.RowDefinitions><br />
<br />
<Border Grid.Row="0" Grid.Column="0" x:Name="b1" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas1"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse1" Fill="Red" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
<Border Grid.Column="0" Grid.Row="1" x:Name="b2" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas2"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse2" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
<Border Grid.Column="0" Grid.Row="2" x:Name="b3" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas3"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse3" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
<Border Grid.Column="1" Grid.Row="1" x:Name="b4" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas4"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse4" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
<Border Grid.Column="1" Grid.Row="2" x:Name="b5" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas5"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse5" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
<Border Grid.Column="2" Grid.Row="0" x:Name="b6" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas6"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse6" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
<Border Grid.Column="2" Grid.Row="1" x:Name="b7" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas7"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse7" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
<Border Grid.Column="2" Grid.Row="2" x:Name="b8" MouseLeftButtonUp="switchPlaces" ><br />
<Canvas x:Name="canvas8"><br />
<Ellipse Width="100" Height="100" x:Name="ellipse8" Visibility="Visible"/><br />
</Canvas><br />
</Border><br />
<br />
</Grid><br />
If anyone has any idea how to fix this..
Thank you
|
|
|
|
|
I must be missing something here...
What is this canvas array and how does it relate to the Silverlight Grid?
How does the managed Silverlight code know about the canvas array?
How are you calling javascript from managed code?
Mark Salsbery
Microsoft MVP - Visual C++
|
|
|
|
|
Hi,
my question is, how do I sort related items of an entity.
In an invoice editing window, I have one ComboBox displaying all customers:
XAML:
<UserControl.Resources>
<CollectionViewSource x:Key="cvsCustomers"
d:DesignSource="{d:DesignInstance local:Customer, CreateList=True}" />
...
</UserControl.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource cvsCustomers}}" .../>
Code-behind:
Dim cvsCustomers As System.Windows.Data.CollectionViewSource
cvsCustomers = CType(Me.FindResource("cvsCustomers"), System.Windows.Data.CollectionViewSource)
Dim qryCustomers = _
From c In myEntities.Customers _
Order By c.CustomerCode
Select c
cvsCustomers.Source = qryCustomers
Now I have a 2nd ComboBox displaying all contact persons of the selected customer. This works fine, but the entries in this second ComboBox are unsorted / sorted by ID.
XAML:
<ComboBox ItemsSource="{Binding Path=myInvoice.Customer.Contacts}" .../> ;
How do I get the list on the 2nd ComboBox sorted?
King regards,
and thanks in advance for tips/suggestions,
Nico
|
|
|
|
|
Hey,
I've been optimising a piece of software with a very large number of simple controls (~30000), most of whom have their parent panel virtualised at any one time (reducing the number in the visual tree to a few hundred). Running through ANTS profiler, I'm told that 60-70% of CPU-time is taken up by UIElement_CreateAutomationPeerDelegate.Invoke and its child methods. A user interacting with the program for 20 seconds or so [creating an additional ~20 of these controls, deleting a few, and moving a few around the page] results in this method being called 5.4 million times. Putting a counter near this method confirms this when run without debugging/ANTS.
As far as I'm aware, I haven't touched AutomationPeer-related methods, and it is used for Accessibility/COM stuff. Can I turn this 'function' off, or are there ways of reducing how many times it is called?
TIA,
Lee
|
|
|
|
|
At the risk of getting 1'ed, I'd say you are trying to band aid things here. Fix the real issue. You shouldn't need 30,000 controls in a single window .
-- Modified Monday, July 18, 2011 7:04 PM
|
|
|
|
|
Haha, well agreed to an extent. The maximum number of controls 'on' the window at any one time is actually around 200 (and usually only around 40) - the rest are held in memory waiting to be connected to the visual tree when scrolling takes place.
I have tried virtualising these 'properly' so that they are re-used but the overhead for them binding to their data is actually more costly than having them just sit in memory (which I certainly found surprising)
|
|
|
|
|