WPF - is there a way to remove specific child from Canvas.Children? - wpf

I am working on a charting control where I am plotting the "analysis range," which is just two vertical lines on the chart. A problem arises when I want to change the analysis range, because I don't know how to remove only the two analysis range lines, so I end up clearing the chart and plotting the actual data values and whatnot again. Is there a way to tag these UI elements (i.e. analysis range is a gridline UI element) so that I can remove them specifically? I suppose I could save the "index" of the UI element somewhere and delete these, but I am wondering if there is a cleaner way of doing this. Thanks a lot.

All UIElements have a UID which is a string. You could set the UID of the range lines to something predictable. Keep in Mind that UID must be unique. Then when you need to remove only the gridlines, you iterate through the Children collection gathering a list of the UI elements that need to be removed, then remove them.
Something like this:
Canvas c = new Canvas();
c.Children.Add( new UIElement() { Uid = "Line1" } );
c.Children.Add( new UIElement() { Uid = "Line2" } );
c.Children.Add( new UIElement() { Uid = "Line3" } );
c.Children.Add( new UIElement() { Uid = "Text1" } ); //This is added as a sample
List<UIElement> itemstoremove = new List<UIElement>();
foreach (UIElement ui in c.Children)
{
if (ui.Uid.StartsWith("Line"))
{
itemstoremove.Add(ui);
}
}
foreach (UIElement ui in itemstoremove)
{
c.Children.Remove(ui);
}
That should work. A quick test of this code in debug shows the Children count at 1, with only the UIElement with Uid of Text1 present in the list.

When you add the two lines to the Canvas, can't you hold a reference to the two lines. That way, when you need to redraw them, just do a Canvas.Children.Remove(line1) and Canvas.Children.Remove(line2). Then update your references for the lines and re-add them to the Canvas. You could even just update the X and Y values of the lines themselves rather than removing and re-adding them. This way, the Chart would just move the lines.
But, basically the key is to maintain a reference to the lines after adding them to the Canvas.

Related

WPF TranslateTransform is ScaleTransforming

In the following code part is Part class's object which is inherited from ScatterViewItem class (Microsoft.Surface.Presentation.Controls). part.Content is Path geometry:
GeometryGroup gGroup = new GeometryGroup
{
FillRule = FillRule.Nonzero
};
foreach(KeyValuePair<int, Part> kv in Layers)
{
geom = part.ShadowPath.RenderedGeometry.Clone();
geom.Transform = new TranslateTransform(0, 0);
gGroup.Children.Add(geom);
}
When I comment geom.Transform = new TranslateTransform(0, 0); line, it shows with right scale but when I uncomment it, although I am not changing anything, it scales paths:
I want to know how to fix it.
#Clemens was right about initial transformation values: there was matrixTransform with automatically calculated values by WPF. When I assigned new transformation values, it totally removed old values. I just picked that matrix and updated some values and it worked fine.

Dynamically populate GraphSource through PopulateGraphSource method - RadDiagram

I am facing an issue where my graph is tree layout and looks fine initially. However, if I choose to change GraphSource upon user input/ clicks using PopulateGraphSource like in the OrgChart example, I get all the nodes stacked on top of each other with no links and all in corner.
I tried resetting graphSource by creating a new one
this.graphSource = new GraphSource();
I also tried to use the Clear method for GraphSource. Neither did solve the problem, I keep having the same issue.
I am using
ObservableCollection<Node> hierarchicalDataSource;
to fill up my GraphSource object.
All I do is create a new one and then call
PopulateGraphSource();
method.
Similar issues: question in telerik support , telerik support different question
Try calling the Layout method on the diagram control. Here is a little fragment of code
TreeLayoutSettings settings = new TreeLayoutSettings()
{
TreeLayoutType = TreeLayoutType.TreeDown,
VerticalSeparation = 60,
HorizontalSeparation=30
};
if (this.diagram.Shapes.Count > 0)
{
settings.Roots.Add(this.diagram.Shapes[0]);
this.diagram.Layout(LayoutType.Tree, settings);
this.diagram.AutoFit();
//this.diagram.Zoom = 1;
}

LoaderMax: setting array as a container (ImageLoader)

So, I have a LoaderMax instance loading images from various URLs. I want to add all loaded images to an array.
Here's my code:
var photosArray:Array = new Array(5);
var imageLoadingQueue:LoaderMax = new LoaderMax({name:"mainQueue", onComplete:completeHandler});
for (var g:uint=0; g<5; g++)
{
imageLoadingQueue.append(new ImageLoader("/img" + g + ".jpg", {name:"photo", container:photosArray[g], noCache:false, smoothing:true, width:126, height:126, scaleMode:"proportionalOutside"}));
}
imageLoadingQueue.load();
private function completeHandler(e:LoaderEvent):void
{
trace("finished loading pictures!");
//the next two lines will return an error (saying that photosArray[1] is null)
stage.addChild(photosArray[1]);
photosArray[1].x = 250;
}
A few problems:
If I set the container of the image being loaded to the Array, it won't work. I'm not being able to access the image inside the array because it says it's null.
If I set the container of the image being loaded to "this" (using the container property when appending a new ImageLoader) and, on the completeHandler, set my array equal to event.target.content, it kinda works (but it's not the ideal). The problem is that, by doing so, the images are appearing on the stage as they are loaded, and I do no want them to do so.
Any help would be heavily appreciated.
Thanks!!
David is correct, but I also wanted to mention that the LoaderMax's "content" is actually an array of all of its children's content, so you could just use that for simplicity. Keep in mind that ImageLoaders automatically create a Sprite (technically called a "ContentDisplay") to drop the image into so you probably don't need to create ANOTHER Sprite (a container for the container).
var photos:Array = imageLoadingQueue.content;
stage.addChild(photos[1]);
The other nice thing is that it creates the ContentDisplay Sprites immediately, even before any content is loaded into them, so you can place them and size them however you want while (or before or after) loading occurs.
The container needs to be a DisplayObjectContainer. ImageLoader will try to add the image to the container using addChild(), so obviously this won't work with an empty array. Create a new Sprite for each image and add it into the array first:
for (var g:uint=0; g<5; g++)
{
photosArray[g] = new Sprite();
imageLoadingQueue.append(new ImageLoader("/img" + g + ".jpg", {name:"photo", container:photosArray[g], noCache:false, smoothing:true, width:126, height:126, scaleMode:"proportionalOutside"}));
}

initialLayoutAttributesForAppearingItemAtIndexPath fired for all visible cells, not just inserted cells

Has anyone seen a decent answer to this problem?
initialLayoutAttributesForAppearingItemAtIndexPath seems to be being called for all visible cells, not just the cell being inserted. According to Apple's own docs:
For moved items, the collection view uses the standard methods to retrieve the item’s updated layout attributes. For items being inserted or deleted, the collection view calls some different methods, which you should override to provide the appropriate layout information
Which doesn't sound like what is happening... the other cells aren't being inserted, they are being moved, but it's calling initialLayoutAttributesForAppearingItemAtIndexPath for the ones being moved too.
I have seen work arounds using prepareForCollectionViewUpdates: to trace which indexPaths are being updated and only changing those, but this seems a bit odd that it's going agains their own docs. Has anyone else found a better way around this?
I found this blog post by Mark Pospesel to be helpful.
The author also fixed WWDC CircleLayout sample and posted it on Github.
Methods of interest:
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
// Keep track of insert and delete index paths
[super prepareForCollectionViewUpdates:updateItems];
self.deleteIndexPaths = [NSMutableArray array];
self.insertIndexPaths = [NSMutableArray array];
for (UICollectionViewUpdateItem *update in updateItems)
{
if (update.updateAction == UICollectionUpdateActionDelete)
{
[self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
}
else if (update.updateAction == UICollectionUpdateActionInsert)
{
[self.insertIndexPaths addObject:update.indexPathAfterUpdate];
}
}
}
- (void)finalizeCollectionViewUpdates
{
[super finalizeCollectionViewUpdates];
// release the insert and delete index paths
self.deleteIndexPaths = nil;
self.insertIndexPaths = nil;
}
// Note: name of method changed
// Also this gets called for all visible cells (not just the inserted ones) and
// even gets called when deleting cells!
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
// Must call super
UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
if ([self.insertIndexPaths containsObject:itemIndexPath])
{
// only change attributes on inserted cells
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Configure attributes ...
attributes.alpha = 0.0;
attributes.center = CGPointMake(_center.x, _center.y);
}
return attributes;
}
// Note: name of method changed
// Also this gets called for all visible cells (not just the deleted ones) and
// even gets called when inserting cells!
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
// So far, calling super hasn't been strictly necessary here, but leaving it in
// for good measure
UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
if ([self.deleteIndexPaths containsObject:itemIndexPath])
{
// only change attributes on deleted cells
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Configure attributes ...
attributes.alpha = 0.0;
attributes.center = CGPointMake(_center.x, _center.y);
attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
}
return attributes;
}
You're not alone. The UICollectionViewLayout header file comments make things a little clearer.
For each element on screen before the invalidation,
finalLayoutAttributesForDisappearingXXX will be called and an
animation setup from what is on screen to those final attributes.
For each element on screen after the invalidation,
initialLayoutAttributesForAppearingXXX will be called an an animation
setup from those initial attributes to what ends up on screen.
Basically finalLayoutAttributesForDisappearingItemAtIndexPath is called for each item on screen before the animation block starts, and initialLayoutAttributesForAppearingItemAtIndexPath is called for each item after the animation block ends. It's up to you to cache the array of UICollectionViewUpdateItem objects sent in prepareForCollectionViewUpdates so you know how to setup the initial and final attributes. In my case I cached the previous layout rectangles in prepareLayout so I knew the correct initial positions to use.
One thing that stumped me for a while is you should use super's implementation of initialLayoutAttributesForAppearingItemAtIndexPath and modify the attributes it returns. I was just calling layoutAttributesForItemAtIndexPath in my implementation, and animations weren't working because the layout positions were different.
If you've subclassed UICollectionViewFlowLayout, you can call the super implementation. Once you've got the default initial layout, you can check for an .alpha of 0. If alpha is anything other than 0, the cell is being moved, if it's 0 it's being inserted.
Bit of a hack, I know, but it works 👍.
Swift 2.0 implementation follows:
override func initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath) where attributes.alpha == 0 else {
return nil
}
// modify attributes for insertion here
return attributes
}
Make sure you're using new method signature in Swift 3. Autocorrection doesn't work for this method:
func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?

flex 3: Can anybody help me optimize this array -> arrayCollection function?

I'm using a parent to pass a multi-dimensional array to a child. Structure of the array, named projectPositions is as follows (with example data):
projectPositions[0][0] = 1;
projectPositions[0][1] = 5;
projectPositions[0][2] = '1AD';
projectPositions[0][3] = 'User name';
I need to take this inherited array and turn it into an arrayCollection so that I can use it as a dataProvider. Currently, my init function (which runs onCreationComplete) has this code in it to handle this task of array -> arrayCollection:
for (var i:int = 0; i < projectPositions.length; i++)
{
tempObject = new Object;
tempObject.startOffset = projectPositions[i][0];
tempObject.numDays = projectPositions[i][1];
tempObject.role = projectPositions[i][2];
tempObject.student = projectPositions[i][3];
positionsAC.addItemAt(tempObject, positionsAC.length);
}
Then, during a repeater, I use positionsAC as the dataprovider and reference the items in the following way:
<mx:Repeater id="indPositions" dataProvider="{positionsAC}" startingIndex="0" count="{projectPositions.length}">
<components:block id="thisBlock" offSet="{indPositions.currentItem.startOffset}" numDays="{indPositions.currentItem.numDays}" position="{indPositions.currentItem.role}" sName="{indPositions.currentItem.student}" />
</mx:Repeater>
This all works fine and returns the desired effect, but the load time of this application is around 10 seconds. I'm 99% sure that the load time is caused by the array -> arrayCollection for loop. Is there an easier way to achieve the desired effect without having to wait so long for the page to load?
The issue your having loading items could be because you are using a repeater instead of a list class.
With a repeater, there will be a block created in memory, and drawn on the screen. So, if you have 100 items in your array, then 100 blocks will be created. this could slow down both initial creation and the overall app.
A list based class focuses on a technique called renderer recycling; which means only the displayed elements are created and rendered on the screen. So, depending on settings, you'd usually have 7-10 'block' instances on the screen, no matter how many items you have in your array.
change
positionsAC.addItemAt(tempObject, positionsAC.length);
to
positionsAC.addItem(tempObject);
addItemAt is causing a reindex of the collection which can greatly slow down the collection.
[EDIT]
Put this trace statement before and after the loop
take the output and subtract one from the other and that will show how many milliseconds the loop has run.
var date:Date = new Date( );
trace( date.getTime())

Resources