As I use the EAInformation screens in AppComplete I found that it would be good to be able to accept multi line input in some grid cells.
As the EAInformation UI is completely built with ViewModels and Styles my first approach was to fix this in the Style. However I had, and still have, a hard time to figure out just how to get the style dynamic enough so that it could look at what it displays so that it can decide if it should accept return or not.
So if you have a different solution for this problem – enlighten me please…
Anyway this is what I have; classes with attributes that has the type “Text”:
These attributes will be treated as System.String in ECO maintained code:
- [UmlElement(Id="84e4b74b-ee5e-4642-9bd3-9152959e9264", Index=Eco_LoopbackIndices.SLAText)]
- [UmlTaggedValue("Eco.PMapper", "StringAsText")]
- [UmlTaggedValue("Eco.Length", "-1")]
- public string SLAText {
- get {
- return ((string)(this.eco_Content.get_MemberByIndex(Eco_LoopbackIndices.SLAText)));
- }
- set {
- this.eco_Content.set_MemberByIndex(Eco_LoopbackIndices.SLAText, ((object)(value)));
- }
- }
But as you see above ECO adds a tagged value that defines the suggested Persistence mapper. I was thinking that I can use this tagged value to allow grid columns that show an attribute with this PMapper to accept return and capture multiline input.
Side note: How does ECO decide to treat Text as String, and How does ECO know to use the StringAsText persistence mapper?
In the install directory ECO finds the EcoDataTypes.XML (C:\Program Files (x86)\CapableObjects\ECO\6.0\config\EcoDataTypes.xml). And in this file ECO finds this listing:
- <Type CanonicalName="Text" Type="System.String">
- <CommonName Name="text" CaseSensitive="false" />
- <CommonName Name="memo" CaseSensitive="false" />
- <LanguageName Language="C#" Name="string" />
- <LanguageName Language="Delphi" Name="string" />
- <LanguageName Language="Oxygene" Name="string" />
- <LanguageName Language="VB" Name="String" />
- <TaggedValue Tag="Eco.PMapper" Value="StringAsText" />
- <TaggedValue Tag="Eco.Length" Value="-1" />
- </Type>
You are free to add your own data types to this file, and also to use your own persistence mappers if you have defined your own.
So back to the main issue – how can I inject my own dynamic behavior in the ViewModel driven UI? This is what I did:
- private void HookAllGridsToEnableReturnInTextColumns(ViewModel.WPF.ViewModelWPFUserControl UC)
- {
- foreach (var x in UC.ViewModel.AllViewModelClasses.Values)
- {
- foreach (var y in x.Columns)
- {
- if (y.SpawnedArtifacts.ContainsKey("grid"))
- {
- (y.SpawnedArtifacts["grid"] as DataGrid).PreviewKeyDown +=
- new KeyEventHandler(EAInformation_PreviewKeyDown);
- }
- }
- }
- }
The code above takes a ViewModelWPFUserControl – finds the ViewModel-runtime that by the way is the same for all supported UI-styles (ASP.NET, Winforms, WPF and Silverlight). It looks for ViewModel columns displayed as Grid components – and when it finds one it hooks the PreviewKeyDown event to a local event handler.
Then the event handler is implemented this way:
- void EAInformation_PreviewKeyDown(object sender, KeyEventArgs e)
- {
- if (e.Key == Key.Return)
- {
- DataGridCellInfo dgci = (sender as DataGrid).CurrentCell;
- ViewModelClass vmc = ((sender as DataGrid).Tag as ViewModelClass);
- foreach (var x in vmc.Columns)
- {
- if (x.SpawnedArtifacts.ContainsKey("control") && x.SpawnedArtifacts["control"] == dgci.Column)
- {
- if (x.ModelInfo != null)
- {
- ITaggedValue tv = x.ModelInfo.TaggedValues.GetItemByTag(EcoTaggedValues.PMapperName.Tag);
- if (tv != null && tv.Value == "StringAsText")
- {
- IInputElement c=Keyboard.FocusedElement;
- if (c is TextBox)
- (c as TextBox).AcceptsReturn = true;
- }
- }
- }
- }
- }
- }
The code above check if Return is pressed, if so, and if CurrentCell belongs to a column that was implemented by a ViewModelColumn that get its data from a model-class-attribute that use a persistence mapper of StringAsText – if all this is true and the focused element is a TextBox (this is the case when the cell is in edit mode) then we instruct the TextBox to accept return-key-strokes.
And that is it. With this article I wanted to show that the ViewModels are by no means a cul-de-sac. They are just a declarative way to get you further in the right direction with less code. You can and should use the meta information from your model to create generic code that does stuff that you want. Not only does this minimize the code you need to maintain – it also raise the quality since code written this way acts the same all over.