Localize node texts in treeview using resource files - c#

For a project I need a tree view that allows the user to select a module, which then is displayed in a content area. The project relies heavily on localization and this is provided by the resource files.
Now I discovered today, that the text that are assigned to preset tree view nodes are not contained in the resource files.
So the question is whether there is a way of doing this, short of mapping the elemenst in code. I.e. assigning a name to the node, running over all nodes and pulling the resources from the resouce manager based on the node name.
This is what I am currently doing, however, it just doesn't "feel" right:
private void TranslateNodes(TreeNodeCollection treeNodeCollection) {
var rm = Resources.ResourceManager;
foreach (TreeNode node in treeNodeCollection) {
node.Text = rm.GetString(node.Name + "_Text");
this.TranslateNodes(node.Nodes);
}
}
Thanks!

Your approach looks ok for me, with one exception, it believes that node.Name is unique though entire treeview (which is not correct in general case).
You can use TreeNode.FullPath for unique identify node within treeview. Or alternatively your code can depend on node tag value, but this is highly depend on usage scenario.
And do not forget about calling TreeView's BeginUpdate-EndUpdate.

No suitable, solution found except the one statete in the op ... so closing the question seems apropriate.

Related

Hierarchical change notifications in an object hierarchy

I have a recursive hierarchy of three kinds of object in a C# library. Let's call them Boxes, Nuts and Bolts. Boxes can contain other Boxes, or Nuts and Bolts. Nuts and Bolts obviously can't contain anything.
Let's assume each Box has ObservableCollections of Box, Nut, and Bolt. Each Nut and Bolt implements INotifyPropertyChanged.
Is there an accepted best practice for propagating notifications of changes to the observable collections, or property changes on any Nut or Bolt to an object which holds a reference to the topmost Box? Or any particular design patterns you would recommend?
EDIT: to give some background to this issue, I lead the Chemistry for Word project. You can see the component that display structures in real time on the left.
Now, believe it or not, this currently draws everything through data binding. Each of those molecule displays on the LHS is an ItemsControl. (And yes, I am using WPF with MVVM!) This has proven to have too many overheads and lack of flexibility for a long-term solution. So I have gone back to generating DrawingVisuals directly. This approach allows much more fine control.
The Boxes, Nuts and Bolts in my original example are Molecules, Atoms and Bonds. If any of these are added, removed or changed then the display has to know about it so it can update. As I have already implemented the interfaces and objects for data binding then I want to exploit the code I already have.
I've had a similar model with fast-path access to upstream Node instances in directed acyclic graphs. A Node has a weak reference to its immediate parent. Node has a property to get the Root...that tries to return its parent's Root. If there's no parent, then that node is a root. Rootness is based solely on containment. Note that the parent isn't a collection...because sometimes the child node isn't even in a collection. Something more-or-less like...
public abstract class Node
{
WeakReference<Node> parent;
public Node Root
{
get { return Parent?.Root ?? this; }
}
public Node Parent
{
get
{
if ( parent != null )
{
if ( parent.TryGetTarget( out Node parentNode ) )
{
return parentNode;
}
}
return this;
}
internal set { /*...*/ } //--> if you're brave...
}
}
Edit:
Regarding WeakReferences...one of the things our graphs can have is references to nodes in other graphs. We have a node resolver service that will fetch those other nodes. These out-looking references are represented by an identity value (a GUID or a Long) and an associated weak reference. This way, we can load the specified node as needed, but not keep it any longer than necessary. The resolver maintains an LRU cache of nodes resolved this way.
If such a resolved reference needs to resolve its own parent, a similar mechanism exists to allow the dependent node to resolve its parent. Even a node's collected child nodes may be lazy loaded via the resolver service (although there are annotations that inform our framework when to lazy-load and when not).
So, the weak references help with all these incidentally-resolved scenarios. Er...more accurately, they help us not screw up the garbage collection in such scenarios.
In some analytical scenarios, we'll have hundreds of thousands of nodes popping in and out. I could imagine similar dynamics in chemistry modeling.
Why don't you call the parent in the change notification. Something like the following pseudo code:
Bolt()
{
NotifyPropertyChanged(property)
{
PropertyChanged(this, property);
}
ChildNutPropertyChanged(Nut child, property)
{
PropertyChanged(this, child + property);
}
}
Nut(Bolt parentBolt)
{
parent = parentBolt;
NotifyPropertyChanged(property)
{
PropertyChanged(this, property);
parent.NotifyPropertyChanged(this, property);
}
}
If you encapsulate your ObservableCollection of Nuts and Bolts and only make a ReadOnlyObservableCollection public, you could make an Add(Nut nut) method (and another one for bolts) that registers to the added Nut's NotifyPropertyChanged event.
This way, you'll know in the Box when a property of a child has changed and take action.

How to fetch diagram object of EA.Element

I have EA element. How can I get the Diagram object or Diagram id for the element's parent?
I am creating EA element through a tabular form. I want to programmatically add this element to the Diagram of parent element.
Parent element is already added to the diagram.
How can I get that parent diagram ?
You can not legally place the same element twice in the same diagram. The automation still (I guess) allows to do it, but is has strange side effects, so you should not attempt it.
If for any reason you want to do so, you need to locate the diagram itself. Since an element can be in more than one diagram, you have to find the relevant diagram or deal with all diagrams where it is placed. To find them you can issue the following:
Repository.SQLquery("SELECT do.diagram_id FROM t_diagramobjects AS do INNER JOIN t_object ON do.Object_ID = t_object.Object_ID WHERE t_object.ea_guid = '{B979A0E9-17CB-4ed2-ADAB-D6FB3BE10A2B}'")
Just replace the example GUID with that of your object. In return you get a XML string that lists all diagram ids where the element is used. This query looks into all diagram objects where your element is used and returns the diagram id of the appropriate diagram.
Now you can do whatever evil you want to do with those diagrams.
Well, honestly there is a legal way to have an element twice on a diagram. This is by using a virtualized connector. I haven't used that (neither manually not per automation) so far except for a short manual test. I can have a look if you need that.

Extending the TreeView control for incremental filtering/searching

I'm trying to extend the winforms TreeView control to allow incremental filtering and searching similar to the Solution Explorer in VS2012/VS2013.
Ideally, I would like it to be capable of replacing the existing TreeView with minimal code change - as far as the consumer is concerned, the only difference would be a method void Filter(string). Because of this, I think it would make sense for the Nodes property to return the TreeNodeCollection with ALL nodes, even ones not showing because of an applied filter.
I have the code written to handle the filtering, and it actually works quite well except when I access base.Nodes, it returns my filtered nodes and not the full list.
The problem I have is, I'm unable to clone or create a new instance of TreeNodeCollection, because the constructor is marked as internal. So my ideal code would look something like this:
public class TreeViewEx : TreeView
{
// results in a compiler error:
private TreeNodeCollection _allNodes = new TreeNodeCollection();
public new TreeNodeCollection Nodes { get { return _allNodes; } }
public TreeNodeCollection FilteredNodes { get { return base.Nodes; } }
public void Filter(string searchString)
{
base.BeginUpdate();
base.Nodes.Clear();
foreach (TreeNode node in FilterInternal(_allNodes, searchString))
{
base.Nodes.Add(node);
}
base.EndUpdate();
}
}
So as you can see, I'm trying to decouple the nodes that are shown in the UI from the nodes that the consumer would access. Of course with TreeNodeCollection having an internal constructor only, I'm unable to create a new instance or clone it.
I considered these two options, but neither sound like good solutions:
Use reflection to instantiate the TreeNodeCollection object (due to the internal constructor) for the second list. This option seems like it would be more efficient than #2, but of course I'm creating an instance of an object I'm not supposed to.
Instantiate a second TreeView in memory and use the Nodes property from that to maintain my second list. This seems like it might be a lot of overhead.
I want the end result to still be a TreeNodeCollection so the TreeView can be used to replace our existing controls with minimal code and we do have several places using the Find method, which doesn't exist in List<TreeNode>.
Does anyone have any recommendations on how to handle this? What about performance/resource-wise with my two considerations?
Thank you
Update 1:
Per Pat's recommendation, I decided to take a step back and avoid messing with Nodes altogether. So now I've added a List<TreeNode> AllNodes property and have the Nodes just display the nodes that appear in the TreeView (the filtered list), so now it's a bit simpler.
My problem now is, how do I know when AllNodes has an item added to it so I can keep Nodes in sync? I've considered using a BindingList so I have the ListChanged event, but then I would need to have my TreeNode and node's children/grand-children/etc (AllNodes[0].Nodes) use a custom class that inherits from TreeNode and change the Nodes property, and TreeNode.Nodes isn't overridable. Is there another way? I could make a new property called NodeExs or something, but that seems very unintuitive and I could see another dev coming along later and pulling his hair out because the Nodes property is there but doesn't work.
With regard to your proposed solutions, #2 is out because a TreeNode cannot belong more than one control. And while it might be possible to create an instance of TreeNodeCollection via reflection, it won't be very useful because its designed to be coupled to a TreeView or another TreeNode. You won't be able to add/remove nodes from the collection.
Because of this, I think it would make sense for the Nodes property to
return the TreeNodeCollection with ALL nodes, even ones not showing
because of an applied filter.
I disagree, the TreeNodeCollection returned by the Nodes property is used by the framework and OS to render the control. You really don't want to hide this property or alter its functionality.
If a consumer needs to have access to _allNodes, create a List<TreeNode> AllNodes property or use a custom collection.
I've found out that the TreeNodeCollection should only be used to read the listed nodes. Instead, I've used List<TreeNode> to list nodes. In my project, I created a List<TreeNode> for each level on the TreeView. I filled the lists at the same time when I filled the TreeView, at the startup. In the end, I used AddRange() to make and combine a list of the all nodes. This way I had all the nodes listed and categorized.
It's easy and fast to create this kinds of lists. I also created a List<string> version of the all nodes list, which I set up as an AutoCompleteCustomSource for my TextBox. This way I was able to use TextBox with AutoComplete for searching the nodes.
I'd make different lists for the consumers and other categories. Then I'd only add the items to the TreeView which meet the given criteria. You can also use treeView.Nodes.Remove() to remove any nodes. You'd still have the actual node stored on the lists, and could add it back again later.
These are just some ideas.

Unit testing simple Tree structure manipulations

Given a very simple structure such as this:
public class TreeNode
{
public int ID { get; set; }
public List<TreeNode> Children { get; set; }
}
TreeNode may have other properties.
And when used in the following manner:
var tree = new List<TreeNode>(); //no root node
If I perform add/update/remove operations on the tree based on certain criteria. For example, removal of a node based on one or more of the other properties I mentioned above, I'd like to compare the tree graph before and after the changes and then via unit tests verify some of the follow:
Tree remains unchanged
Specified nodes are removed
Specified nodes are added
Specified nodes are updated
The 3 above whilst also verifying that the rest of the tree is unchanged.
Ideally, I'd throw an expection listing the nodes that were not found, not expected etc. However, at this stage I'd be happy with a true/false to my check.
Are there any known patterns/alogorithms existing projects that would help with this?
I am happy for pseudo-code or examples in other languages as long as they don't rely on features I can't replicate in .NET.
My tree is unlikely to get to more than 7 or 8 levels deep and no more than a hundred nodes in total as it will be test data so brute force looping is fine and performance isn't a consideration at this time.
I'm really looking for tips, tricks, advice, code on how to approach this.
TIA
When I did unit tests for tree structures, I simply built an ad-hoc tree of already known structure, execute operations on it and verified that the changes are exactly the ones I expected, a very simple but usable method, if you create good test cases.
Regardless my experience, you may think of some recursive comparison methods for tree nodes that may return a list of children nodes which are different. So the basic idea is to maintain two equal trees, perform operation on one of them, then check what was changed.
If you don't have any UI that shows the tree, I'd also recommend to make visualizations of a tree, using http://www.graphviz.org/ , you may generate pictures of your tree before and after some operation, so you will see how whole structure was changed(not usable for unit tests, but anyway).
And the last thing, I suggest to have a root node, it will simplify your recursive algorithms. If you don't have root, because of some requirments for UI or so, you may modify that part to simply ignore the root.
You can also have a function that get the string representation of the tree and simply compare 2 string representations instead of comparing 2 trees
I did that earlier this week
example function (swift)
public var description: String {
var s = "\(value)"
if !children.isEmpty {
s += " {" + children.map { "\($0.description)"}.joined(separator: ", ") + "}"
}
return s
}
You can test it like this
XCTAssert ( tree.description == "beverages {hot {tea {black, green, chai}, coffee, cocoa}, cold {soda {ginger ale, bitter lemon}, milk}}");

remove namespace from childnode created element

In order to manipulate the dlls thats used inside of my VS project I have to update the reference that the dll is pointed to. I do this via modifying the XML of the csproh (yes this works).
However, this time around I want to add the a SpecificVersion reference so <SpecificVersion xmlns="">False</SpecificVersion> however you cannot build using microsoft build engine when you have a custom xml namespace beneath the Reference element. How do you remove the xmlns="" inside of the SpecificVersion node?
<SpecificVersion xmlns="">False</SpecificVersion>
XmlElement SpecificVersionElement = refNode.OwnerDocument.CreateElement("SpecificVersion");
SpecificVersionElement.InnerText = "False";
refNode.AppendChild(SpecificVersionElement);
Remarkable, like usual 30 seconds after I post something I figure it out. And in this case it makes no sense. By specifying a namespace, the createelement effectively removes the namespace.
XmlElement SpecificVersionElement = refNode.OwnerDocument.CreateElement("SpecificVersion","http://schemas.microsoft.com/developer/msbuild/2003");
SpecificVersionElement.InnerText = "False";
refNode.AppendChild(SpecificVersionElement);
Of course this is probably hidden somewhere deep in a MSDN article

Categories