Memory leak on CanExecuteChanged using Catel Command - c#

I was profiling a WPF application that uses Catel as MVVM Framework and I've seen that in a ViewModel I've got 2 retention path as
Now I create such RadMenuItem in a behavior I attach to the context menu and they're defined as
protected virtual IEnumerable<RadMenuItem> GetRowMenuItems(RadContextMenu contextMenu)
{
var rowItems = new List<RadMenuItem>();
RadMenuItem saveSettings = new RadMenuItem
{
Tag = "force",
Header = CoreResources.LBL_SAVE_SETTINGS,
Command = DefaultRtViewContextMenuCommands.SaveLayoutDataCommand,
CommandParameter = AssociatedObject,
Icon = new Image { Source = new BitmapImage(new Uri("pack://application:,,,/IF.Tesoreria.Client.WPF.Core;component/Media/save.png")) }
};
rowItems.Add(saveSettings);
RadMenuItem loadSettings = new RadMenuItem
{
Tag = "force",
Header = CoreResources.LBL_LOAD_SETTINGS,
Command = DefaultRtViewContextMenuCommands.LoadLayoutDataCommand,
CommandParameter = AssociatedObject,
Icon = new Image { Source = new BitmapImage(new Uri("pack://application:,,,/IF.Tesoreria.Client.WPF.Core;component/Media/load.png")) }
};
Now The commands are defined in
public class DefaultRtViewContextMenuCommands
{
public static Command<FlexGridCommandParameter> SaveLayoutDataCommand { get; private set; }
public static Command<FlexGridCommandParameter> LoadLayoutDataCommand { get; private set; }
private static void OnLoadLayoutCommandExecute(FlexGridCommandParameter grid)
{
Argument.IsNotNull(() => grid);
var dependencyResolver = DependencyResolverManager.Default;
var openFileService = dependencyResolver.DefaultDependencyResolver.Resolve<IOpenFileService>();
openFileService.Filter = "Gridview setting file|*.flexgrid";
if (openFileService.DetermineFile())
{
// User selected a file
using (var fs = File.OpenRead(openFileService.FileName))
{
GridViewPersistenceHelper.LoadLayout(grid.Grid, fs, null);
}
}
}
private static void OnSaveLayoutCommandExecute(FlexGridCommandParameter grid)
{
Argument.IsNotNull(() => grid);
var dependencyResolver = DependencyResolverManager.Default;
var saveFileService = dependencyResolver.DefaultDependencyResolver.Resolve<ISaveFileService>();
saveFileService.Filter = "Gridview setting file|*.flexgrid";
if (saveFileService.DetermineFile())
{
// User selected a file
using (var fs = File.OpenWrite(saveFileService.FileName))
{
GridViewPersistenceHelper.SaveLayout(grid.Grid, fs);
}
}
}
static DefaultRtViewContextMenuCommands()
{
viewModelFactory = ServiceLocator.Default.ResolveType<IViewModelFactory>();
portfolioService = ServiceLocator.Default.ResolveType<IPortfoliosService>();
pkInstrumentsService = ServiceLocator.Default.ResolveType<IPkInstrumentsService>();
SaveLayoutDataCommand = new Command<FlexGridCommandParameter>(OnSaveLayoutCommandExecute,_=>true);
LoadLayoutDataCommand = new Command<FlexGridCommandParameter>(OnLoadLayoutCommandExecute,_=>true);
}
What am I doing wrong?
Thanks

radMenuItem.Command = null;
Works for me. You can decompile and see that when you do it, the menu item unregisters himself from the Command’s CanExecuteChanged

Related

Xamarin XAML to C# Data Trigger

In reference to this question below I have XAML working, but am struggling with converting to C#
Xamarin / MAUI XAML to C#
Here is my attempt that isn't working and I'm not sure why... My goal is when LayoutState.Success it updated via VenuePageViewModel, VenueSuccessContentView should display inside the cvWrap ContentView on the Page.
public partial class VenuePageViewModel : ObservableObject
{
[ObservableProperty]
private LayoutState layoutState = LayoutState.Loading;
public VenuePageViewModel()
{
LayoutState = LayoutState.Success;
}
}
public class VenueSuccessContentView : ContentView
{
public VenueSuccessContentView()
{
Content = new Label() { Text = "hello world", TextColor = Colors.Red };
}
}
public class MainPageCS : ContentPage
{
public MainPageCS()
{
BindingContext = new VenuePageViewModel();
var venueSuccessCV = new VenueSuccessContentView();
Resources.Add(nameof(LayoutState.Success), venueSuccessCV);
var cvWrap = new ContentView();
var cv = new ContentView();
cvWrap.Content = cv;
Content = cvWrap;
var datatrigger = new DataTrigger(typeof(ContentView))
{
Binding = new Binding(source: RelativeBindingSource.TemplatedParent, path: nameof(VenuePageViewModel.LayoutState)),
Value = LayoutState.Success,
Setters = {
new Setter { Property = ContentView.ContentProperty, Value = venueSuccessCV },
}
};
cvWrap.Triggers.Add(datatrigger);
}
}
Minimal Repo: https://github.com/aherrick/DataTriggerCSharpMaui
The binding path is incorrect in DataTrigger .
Modify Binding property of DataTrigger as below .
Binding = new Binding( path: nameof(VenuePageViewModel.LayoutState))

How to acces object inside CarouselView Xamarin.Forms

I have a Xamarin.Forms app that displays a ViewFlipper (https://github.com/TorbenK/ViewFlipper) inside a CarouselView.
I would like the ViewFlipper to flip back to the front when changing pages inside the carousel. But I can't seem to figure out how to access the ViewFlipper.
I have the following working code:
public class CarouselContent
{
public string FrontImg { get; set; }
public string BackImg { get; set; }
}
public class MainPage : ContentPage
{
public MainPage()
{
var pages = new ObservableCollection<CarouselContent>();
var page1 = new CarouselContent();
page1.FrontImg = "page1Front";
page1.BackImg = "page1Back";
var page2 = new CarouselContent();
page2.FrontImg = "page2Front";
page2.BackImg = "page2Back";
pages.Add(page1);
pages.Add(page2);
var carouselView = new Carousel(pages);
Content = carouselView;
}
}
public class Carousel : AbsoluteLayout
{
private DotButtonsLayout dotLayout;
private CarouselView carousel;
public Carousel(ObservableCollection<CarouselContent> pages)
{
carousel = new CarouselView();
var template = new DataTemplate(() =>
{
//create page
var absLayout = new AbsoluteLayout();
//create images for the flipper
var frontImg = new Image
{
Aspect = Aspect.AspectFit
};
frontImg.SetBinding(Image.SourceProperty, "FrontImg");
var backImg = new Image
{
Aspect = Aspect.AspectFit
};
backImg.SetBinding(Image.SourceProperty, "BackImg");
//create flipper
var flipper = new ViewFlipper.FormsPlugin.Abstractions.ViewFlipper();
flipper.FrontView = frontImg;
flipper.BackView = backImg;
//Add flipper to page
absLayout.Children.Add(flipper);
return absLayout;
});
carousel.ItemsSource = pages;
carousel.ItemTemplate = template;
Children.Add(carousel);
}
}
I tried adding the ViewFlipper to the CarouselContent but I couldn't get that to work. Any ideas?
EDIT:
I tried creating an AbsoluteLayout with bindable items and bind the items created in CarouselContent in the datatemplate of the CarouselView, but the line '(b as BindableAbsLayout).Children.Add((View)v);' in BindableAbsLayout is never called. What am I doing wrong?
class BindableAbsLayout : AbsoluteLayout
{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create(nameof(Items), typeof(ObservableCollection<View>), typeof(BindableAbsLayout), null,
propertyChanged: (b, o, n) =>
{
(n as ObservableCollection<View>).CollectionChanged += (coll, arg) =>
{
switch (arg.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var v in arg.NewItems)
(b as BindableAbsLayout).Children.Add((View)v);
break;
case NotifyCollectionChangedAction.Remove:
foreach (var v in arg.NewItems)
(b as BindableAbsLayout).Children.Remove((View)v);
break;
}
};
});
public ObservableCollection<View> Items
{
get { return (ObservableCollection<View>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
}
public class CarouselContent
{
private ViewFlipper.FormsPlugin.Abstractions.ViewFlipper _flipper;
private ObservableCollection<View> _items;
public ObservableCollection<View> Items
{
get { return _items; }
}
public CarouselContent(string frontImgStr, string backImgStr)
{
_items = new ObservableCollection<View>();
_flipper = new ViewFlipper.FormsPlugin.Abstractions.ViewFlipper();
var frontImg = new Image
{
Aspect = Aspect.AspectFit
};
frontImg.Source = frontImgStr;
var backImg = new Image
{
Aspect = Aspect.AspectFit
};
backImg.Source = backImgStr;
_flipper.FrontView = frontImg;
_flipper.BackView = backImg;
AbsoluteLayout.SetLayoutBounds(_flipper, new Rectangle(0.5, 0.05, 0.85, 0.85));
AbsoluteLayout.SetLayoutFlags(_flipper, AbsoluteLayoutFlags.All);
Items.Add(_flipper);
}
}
public class Carousel : AbsoluteLayout
{
private DotButtonsLayout dotLayout;
private CarouselView carousel;
public Carousel(ObservableCollection<CarouselContent> pages)
{
carousel = new CarouselView();
var template = new DataTemplate(() =>
{
var absLayout = new BindableAbsLayout();
absLayout.BackgroundColor = Color.FromHex("#68BDE4");
absLayout.SetBinding(BindableAbsLayout.ItemsProperty,"Items");
return absLayout;
});
carousel.ItemsSource = pages;
carousel.ItemTemplate = template;
Children.Add(carousel);
}
}
Not sure what the best practice is here, but you could try accessing it via the ItemSelected Event (which fires every time you change back and forth in the carouselview)
Wire it up
carousel.ItemSelected += carouselOnItemSelected;
Get your ViewFlipper
private void carouselOnItemSelected(object sender, SelectedItemChangedEventArgs selectedItemChangedEventArgs)
{
CarouselContent carouselContent = selectedItemChangedEventArgs.SelectedItem;
ViewFlipper viewFlipper = carouselContent.Children[0];
viewFlipper.FlipState = ViewFlipper.FrontView;
}

Implementing Xamarin Forms context actions

I am trying to implement context actions on my list in Xamarin Forms but can't get it to work.
I am not using XAML, but instead creating my layout in code.
I am trying to follow the steps in https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/interactivity/#Context_Actions and I want to push a new page when "Edit" is clicked.
I cleaned up my code and removed my feeble attempts to make things work.
So this is my custom list cell:
public class PickerListCell : TextCell
{
public PickerListCell ()
{
var moreAction = new MenuItem { Text = App.Translate ("Edit") };
moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
moreAction.Clicked += async (sender, e) => {
var mi = ((MenuItem)sender);
var option = (PickerListPage.OptionListItem)mi.CommandParameter;
var recId = new Guid (option.Value);
// This is where I want to call a method declared in my page to be able to push a page to the Navigation stack
};
ContextActions.Add (moreAction);
}
}
And here is my model:
public class OptionListItem
{
public string Caption { get; set; }
public string Value { get; set; }
}
And this is the page:
public class PickerPage : ContentPage
{
ListView listView { get; set; }
public PickerPage (OptionListItem [] items)
{
listView = new ListView () ;
Content = new StackLayout {
Children = { listView }
};
var cell = new DataTemplate (typeof (PickerListCell));
cell.SetBinding (PickerListCell.TextProperty, "Caption");
cell.SetBinding (PickerListCell.CommandParameterProperty, "Value");
listView.ItemTemplate = cell;
listView.ItemsSource = items;
}
// This is the method I want to activate when the context action is called
void OnEditAction (object sender, EventArgs e)
{
var cell = (sender as Xamarin.Forms.MenuItem).BindingContext as PickerListCell;
await Navigation.PushAsync (new RecordEditPage (recId), true);
}
}
As you can see by my comments in the code, I have indicated where I believe things are missing.
Please assist guys!
Thanks!
Probably is too late for you, but can help others. The way i've found to do this is passing the instance of page on creation the ViewCell.
public class PickerListCell : TextCell
{
public PickerListCell (PickerPage myPage)
{
var moreAction = new MenuItem { Text = App.Translate ("Edit") };
moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
moreAction.Clicked += async (sender, e) => {
var mi = ((MenuItem)sender);
var option = (PickerListPage.OptionListItem)mi.CommandParameter;
var recId = new Guid (option.Value);
myPage.OnEditAction();
};
ContextActions.Add (moreAction);
}
}
So, in your page:
public class PickerPage : ContentPage
{
ListView listView { get; set; }
public PickerPage (OptionListItem [] items)
{
listView = new ListView () ;
Content = new StackLayout {
Children = { listView }
};
var cell = new DataTemplate(() => {return new PickerListCell(this); });
cell.SetBinding (PickerListCell.TextProperty, "Caption");
cell.SetBinding (PickerListCell.CommandParameterProperty, "Value");
listView.ItemTemplate = cell;
listView.ItemsSource = items;
}
void OnEditAction (object sender, EventArgs e)
{
var cell = (sender as Xamarin.Forms.MenuItem).BindingContext as PickerListCell;
await Navigation.PushAsync (new RecordEditPage (recId), true);
}
}
Ok, so with the help of some posts, specifically this one https://forums.xamarin.com/discussion/27881/best-practive-mvvm-navigation-when-command-is-not-available, I came to the following solution, though I'm not perfectly satisfied with the way it looks.
My custom cell now announces when a command is being executed using MessagingCenter:
public class PickerListCell : TextCell
{
public PickerListCell ()
{
var moreAction = new MenuItem { Text = App.Translate ("Edit") };
moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
moreAction.Clicked += async (sender, e) => {
var mi = ((MenuItem)sender);
var option = (PickerListPage.OptionListItem)mi.CommandParameter;
var recId = new Guid (option.Value);
// HERE I send a request to open a new page. This looks a
// bit crappy with a magic string. It will be replaced with a constant or enum
MessagingCenter.Send<OptionListItem, Guid> (this, "PushPage", recId);
};
ContextActions.Add (moreAction);
}
}
And in my PickerPage constructor I added this subscription to the Messaging service:
MessagingCenter.Subscribe<OptionListItem, Guid> (this, "PushPage", (sender, recId) => {
Navigation.PushAsync (new RecordEditPage (recId), true);
});
All this works just find, but I'm not sure if this is the way it was intended to. I feel like the binding should be able to solve this without the Messaging Service, but I can't find out how to bind to a method on the page, only to a model, and I don't want to pollute my model with methods that have dependencies on XF.

Programmatically binding TreeViewItem to List of custom object

How I can set programmatically Binding "Header" property of newTableList.Items elements to TableModel.TABLE_NAME ?
foreach (SchemaModel schema in connection.schemas)
{
TreeViewItem newSchema = new TreeViewItem()
{
Header = schema.SCHEMA_NAME.ToString()
};
Binding newTableBinding = new Binding();
newTableBinding.Source = schema.tables;
TreeViewItem newTableList = new TreeViewItem()
{
Header = "Tables",
};
BindingOperations.SetBinding( newTableList, TreeViewItem.ItemsSourceProperty, newTableBinding);
newSchema.Items.Add(newTableList);
newTVI.Items.Add(newSchema);
}
My old, very slow code looks like that:
foreach (TableModel table in schema.tables)
{
newTableList.Items.Add(new TreeViewItem()
{
Header = table.TABLE_NAME.ToString()
});
}
OLD TOPIC ( FOR BETTER VIEW )
I try to build custom TreeView and change my "VERY SLOW METHOD" with fastest with Binding to list of custom objects.
I have SchemaModel which contains
List<TableModel> tables
and every TableModel have
string TABLE_NAME.
My previous very slow method Was :
/* VERY SLOW METHOD !!! */
//foreach (TableModel table in schema.tables)
//{
// newTableList.Items.Add(new TreeViewItem()
// {
// Header = table.TABLE_NAME.ToString()
// });
//}
Creating each time TreeViewItem is slowing my UI which I cannot repair with multitasking.
I decided to programmatically Bind to list of TableModels like that :
Binding newTableBinding = new Binding();
newTableBinding.Source = schema.tables;
TreeViewItem newTableList = new TreeViewItem()
{
Header = "Tables",
// ItemsSource = schema.tables // also works
};
BindingOperations.SetBinding( newTableList, TreeViewItem.ItemsSourceProperty, newTableBinding);
How Can i Bind the Header property to "TABLE_NAME" for Items based on schema.tables list?
My full code
Code:
foreach (ConnectionModel connection in aliases)
{
TreeViewItem newTVI = new TreeViewItem() { Header = connection.alias.ToString() };
foreach (SchemaModel schema in connection.schemas)
{
TreeViewItem newSchema = new TreeViewItem() { Header = schema.SCHEMA_NAME.ToString() };
Binding newTableBinding = new Binding();
newTableBinding.Source = schema.tables;
// newTableBinding.Path = new PropertyPath("TABLE_NAME");
TreeViewItem newTableList = new TreeViewItem()
{
Header = "Tables",
// ItemsSource = schema.tables
};
BindingOperations.SetBinding( newTableList, TreeViewItem.ItemsSourceProperty, newTableBinding);
TreeViewItem newIndexList = new TreeViewItem() { Header = "Indexes" };
/* VERY SLOW METHOD !!! */
//foreach (TableModel table in schema.tables)
//{
// newTableList.Items.Add(new TreeViewItem()
// {
// Header = table.TABLE_NAME.ToString()
// });
//}
newSchema.Items.Add(newTableList);
newSchema.Items.Add(newIndexList);
newTVI.Items.Add(newSchema);
}
tmpAliasTree.Items.Add(newTVI);
}
tmpAliasTree is my TreeView.
My ConnectionModel
[Serializable()]
public class ConnectionModel
{
private int _id;
private string _dsn;
private string _alias ;
private string _host ;
private string _port ;
private string _database;
private string _username;
private string _password;
public List<SchemaModel> schemas = new List<SchemaModel>();
}
My SchemaModel :
[Serializable()]
public class SchemaModel
{
[System.Xml.Serialization.XmlElement("SCHEMA_NAME")]
public string SCHEMA_NAME { get; set; } = "";
[XmlArray("tables"), XmlArrayItem("TableModel", typeof(TableModel), ElementName = "TableModel")]
public List<TableModel> tables = new List<TableModel>();
}
My TableModel
[Serializable()]
public class TableModel
{
[System.Xml.Serialization.XmlElement("TABLE_CAT")]
public string TABLE_CAT { get; set; } = "";
[System.Xml.Serialization.XmlElement("TABLE_SCHEM")]
public string TABLE_SCHEM { get; set; } = "";
[System.Xml.Serialization.XmlElement("TABLE_NAME")]
public string TABLE_NAME { get; set; } = "";
[System.Xml.Serialization.XmlElement("TABLE_TYPE")]
public string TABLE_TYPE { get; set; } = "";
[System.Xml.Serialization.XmlElement("REMARKS")]
public string REMARKS { get; set; } = "";
}
Thank You for any advise.
Although I agree that you should consider moving your view definition to XAML, you can achieve what you're asking by utilizing ItemsControl.ItemContainerStyle property (both TreeView and TreeViewItem derive from ItemsControl). Basically, you need to define a style targeting TreeViewItem and add a setter for TreeViewItem.HeaderProperty with value holding an appropriate binding, and then assign that style either to your tree view, or particular items (depending on your needs). Here's an example:
TreeViewItem newTVI = new TreeViewItem() { Header = connection.alias.ToString() };
var tableModelItemStyle = new Style(typeof(TreeViewItem));
tableModelItemStyle.Setters.Add(new Setter
{
Property = TreeViewItem.HeaderProperty,
//since items will present instances of TableModel, the DataContext will hold
//the model, so we can define the binding using only the property name
Value = new Binding("TABLE_NAME"),
});
foreach(...)
{
...
TreeViewItem newTableList = new TreeViewItem
{
...
ItemContainerStyle = tableModelItemStyle,
};
...
}
If you want to set the style for all items in the tree view (which I do not recommend), you can do it like so:
newTVI.ItemContainerStyle = tableModelItemStyle;

Replacing a method node using Roslyn

While exploring Roslyn I put together a small app that should include a trace statement as the first statement in every method found in a Visual Studio Solution. My code is buggy and is only updating the first method.
The line that is not working as expected is flagged with a “TODO” comment. Please, advise.
I also welcome style recommendations that would create a more streamlined/readable solution.
Thanks in advance.
...
private void TraceBtn_Click(object sender, RoutedEventArgs e) {
var myWorkSpace = new MyWorkspace("...Visual Studio 2012\Projects\Tests.sln");
myWorkSpace.InjectTrace();
myWorkSpace.ApplyChanges();
}
...
using System;
using System.Linq;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
namespace InjectTrace
{
public class MyWorkspace
{
private string solutionFile;
public string SolutionFile {
get { return solutionFile; }
set {
if (string.IsNullOrEmpty(value)) throw new Exception("Invalid Solution File");
solutionFile = value;
}
}
private IWorkspace loadedWorkSpace;
public IWorkspace LoadedWorkSpace { get { return loadedWorkSpace; } }
public ISolution CurrentSolution { get; private set; }
public IProject CurrentProject { get; private set; }
public IDocument CurrentDocument { get; private set; }
public ISolution NewSolution { get; private set; }
public MyWorkspace(string solutionFile) {
this.SolutionFile = solutionFile;
this.loadedWorkSpace = Workspace.LoadSolution(SolutionFile);
}
public void InjectTrace()
{
int projectCtr = 0;
int documentsCtr = 0;
int transformedMembers = 0;
int transformedClasses = 0;
this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
this.NewSolution = this.CurrentSolution;
//For Each Project...
foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
{
CurrentProject = NewSolution.GetProject(projectId);
//..for each Document in the Project..
foreach (var docId in CurrentProject.DocumentIds)
{
CurrentDocument = NewSolution.GetDocument(docId);
var docRoot = CurrentDocument.GetSyntaxRoot();
var newDocRoot = docRoot;
var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
IDocument newDocument = null;
//..for each Class in the Document..
foreach (var #class in classes) {
var methods = #class.Members.OfType<MethodDeclarationSyntax>();
//..for each Member in the Class..
foreach (var currMethod in methods) {
//..insert a Trace Statement
var newMethod = InsertTrace(currMethod);
transformedMembers++;
//TODO: PROBLEM IS HERE
newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod);
}
if (transformedMembers != 0) {
newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
transformedMembers = 0;
transformedClasses++;
}
}
if (transformedClasses != 0) {
NewSolution = NewSolution.UpdateDocument(newDocument);
transformedClasses = 0;
}
documentsCtr++;
}
projectCtr++;
if (projectCtr > 2) return;
}
}
public MethodDeclarationSyntax InsertTrace(MethodDeclarationSyntax currMethod) {
var traceText =
#"System.Diagnostics.Trace.WriteLine(""Tracing: '" + currMethod.Ancestors().OfType<NamespaceDeclarationSyntax>().Single().Name + "." + currMethod.Identifier.ValueText + "'\");";
var traceStatement = Syntax.ParseStatement(traceText);
var bodyStatementsWithTrace = currMethod.Body.Statements.Insert(0, traceStatement);
var newBody = currMethod.Body.Update(Syntax.Token(SyntaxKind.OpenBraceToken), bodyStatementsWithTrace,
Syntax.Token(SyntaxKind.CloseBraceToken));
var newMethod = currMethod.ReplaceNode(currMethod.Body, newBody);
return newMethod;
}
public void ApplyChanges() {
LoadedWorkSpace.ApplyChanges(CurrentSolution, NewSolution);
}
}
}
The root problem of you code is that newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod); somehow rebuilds newDocRoot internal representation of code so next currMethod elements won't be find in it and next ReplaceNode calls will do nothing. It is a situation similar to modifying a collection within its foreach loop.
The solution is to gather all necessary changes and apply them at once with ReplaceNodes method. And this in fact naturally leads to simplification of code, because we do not need to trace all those counters. We simply store all needed transformation and apply them for whole document at once.
Working code after changes:
public void InjectTrace()
{
this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
this.NewSolution = this.CurrentSolution;
//For Each Project...
foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
{
CurrentProject = NewSolution.GetProject(projectId);
//..for each Document in the Project..
foreach (var docId in CurrentProject.DocumentIds)
{
var dict = new Dictionary<CommonSyntaxNode, CommonSyntaxNode>();
CurrentDocument = NewSolution.GetDocument(docId);
var docRoot = CurrentDocument.GetSyntaxRoot();
var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
//..for each Class in the Document..
foreach (var #class in classes)
{
var methods = #class.Members.OfType<MethodDeclarationSyntax>();
//..for each Member in the Class..
foreach (var currMethod in methods)
{
//..insert a Trace Statement
dict.Add(currMethod, InsertTrace(currMethod));
}
}
if (dict.Any())
{
var newDocRoot = docRoot.ReplaceNodes(dict.Keys, (n1, n2) => dict[n1]);
var newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
NewSolution = NewSolution.UpdateDocument(newDocument);
}
}
}
}

Categories

Resources