Silverlight ScrollViewer not updating after zoom - zooming

I have a Silverlight grid with a bunch of content in it (rectangles, textBlocks, etc.) which represents content in a room. Because it gets pretty complex, I decided I needed an ability to "zoom-in" on the grid. I found some good code to do that, but the problem is that after zooming the grids associated ScrollViewer doesn't scroll the full distance down or to the right. How can I force it to update so that I can scroll to the bottom and all the way to the right?
If it helps, here's the code to permit zooming of my Grid:
var style = new Style(typeof(Grid));
var scale = new ScaleTransform();
scale.CenterX = .5;
scale.CenterY =.5;
scale.ScaleX = Scale;
scale.ScaleY = Scale;
var rs = new Setter();
rs.Property = DataGridCell.RenderTransformProperty;
rs.Value = scale;
style.Setters.Add(rs);
OtdrPatchLocationGrid.Style = style;
and here is the XAML that shows the grid and the scroll viewer
<ScrollViewer Name="scViewer" Grid.Row="1" Visibility="Visible" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
<Grid x:Name="OtdrPatchLocationGrid" MinHeight="350" VerticalAlignment="Stretch" Background="Yellow" Grid.Row="1" Grid.Column="0" Margin="0" MouseDown="OtdrRackViewer_MouseDown">
</Grid>
</ScrollViewer>

I'm working on the same issue now,
the ScrollViewer is affected only by the change in Width or Height,
so to fix the problem, you have to do as the following:
suppose we got the Grid or Canvas named (ZoomCanvas)
in the code behind:
double initialCanvasHeight;
double initialCanvasWidth;
public void MainPage() //As the Constructor
{
initialCanvasHeight = ZoomCanvas.Height;
initialCanvasWidth = ZoomCanvas.Width;
}
ZoomCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
/*Assuming you have the scaling code here and the object CanvasScale is used to scale the canvas*/
foreach (var node in ZoomCanvas)
{
var nodeTop = Canvas.GetTop(node);
var nodeLeft = Canvas.GetLeft(node);
if(mostTopValue < nodeTop)
mostTopValue = nodeTop;
if(mostLeftValue < nodeLeft)
mostLeftValue = nodeLeft;
var desiredHeight = (mostTopValue + NodeHeight)*canvasScale.ScaleY;
var desiredWidth = (mostLeftValue + NodeWidth) * canvasScale.ScaleX;
if (desiredHeight > canvasInitialHeight)
{
while (heightToIncrease < desiredHeight)
heightToIncrease += 10;
ZoomCanvas.Height = heightToIncrease;
}
else
while (ZoomCanvas.Height > canvasInitialHeight)
ZoomCanvas.Height -= 10;
if (desiredWidth > canvasInitialWidth)
{
while (widthToIncrease < desiredWidth)
widthToIncrease += 10;
ZoomCanvas.Width = widthToIncrease;
}
else while (ZoomCanvas.Height > canvasInitialHeight)
ZoomCanvas.Width -= 10;
}
scrollViewer.UpdateLayout();
}

Related

My bar charts overlap eachother. Need help to solve it

im making a code where I have an input field and a button on my screen. In the code below btnLeggTil (its norwegian) adds the number you wrote in the input field. It determines the height of the bars. My code is supposed to let the user add bars of whatever the height he/she prefers. As you can see I have put alot of my code inside a loop. The problem with the code is that the bars it makes, overlap eachother. I need to have a space between each bar, but don't know how. Thanks in advance! You can test out the code yourself and see (just remember to make a button and input field with names btnLeggTil and txtInn.
("høyde" means height) ("bredde" means width) ("verdier" means values) sorry its all norwegian
var verdier:Array = new Array();
btnLeggTil.addEventListener(MouseEvent.CLICK, leggtil);
function leggtil (evt:MouseEvent)
{
verdier.push(txtInn.text);
var totHøyde:int = 200; //total height on diagram
var totBredde:int = 450; //total width on diagram
var antall:int = verdier.length;
var xv:int = 50;
var yb:int = 350;
var bredde:int = (totBredde/antall) * 0.8;
var mellom:int = (totBredde/antall) * 0.2;
var maksHøyde:int = maksVerdi(verdier);
function maksVerdi(arr:Array):Number //finds the biggest value in the array
{
//copies to not destroy the order in the original
var arrKopi:Array = arr.slice();
arrKopi.sort(Array.NUMERIC|Array.DESCENDING);
return arrKopi[0];
}
for(var i:int = 0; i < verdier.length; i++)
{
graphics.lineStyle(2, 0x000000);
graphics.beginFill(0x00ff00);
graphics.drawRect(xv + (bredde+mellom)*i, yb, bredde, -verdier[i] * (totHøyde/maksHøyde));
graphics.endFill();
var txtTall = new TextField();
txtTall.x = xv + (bredde+mellom)*i + 5;
txtTall.y = yb - verdier[i] - 10;
txtTall.type = "dynamic";
txtTall.text = verdier[i];
addChild(txtTall);
}
}
You need to modify your for loop a bit,
graphics.clear();
graphics.lineStyle(2, 0x000000);
graphics.beginFill(0x00ff00);
for(var i:int = 0; i < verdier.length; i++)
{
graphics.drawRect(xv + (bredde+mellom)*i, yb, bredde, -verdier[i] * (totHøyde/maksHøyde));
var txtTall = new TextField();
txtTall.x = xv + (bredde+mellom)*i + 5;
txtTall.y = yb - verdier[i] - 10;
txtTall.type = "dynamic";
txtTall.text = verdier[i];
addChild(txtTall);
}
graphics.endFill();

Zooming in AS ScrollPane but I need to center on Mouse Location

I'm working in Flash ActionScript 3. I have a stage with several options to click. Clicking on each one opens a scroll pane (full screen 1920x1080) with an image (images vary in size, but all start at a height of 1080, and the width is usually 4000 or more.
I allow the user to scrollDrag to pan around and view the full image. I have also added a zoom feature, where upon using the scroll wheel on the mouse, the image will zoom in and out a little.
What my problem is, I need to find a way to make it so that the zoom actually zooms in on the location where the mouse it when the wheel move is triggered.
I currently only let the zoom happen to a certain level (in and out).
I've tried several things involving content.x/y, content.width/height, horizontalScrollPosition and maxHorizontalScrollPosition, and I just can't seem to find the right formula to focus in on the right area.
I add the eventListener for the mouse wheel to my aSp2 Scrollpane, and here is the function it triggers:
function onMouseWheelEvent(MouseEvent):void{
var mouseXPos = MouseEvent.stageX;
var mouseYPos = MouseEvent.stageY;
var imageWidth = aSp2.content.width;
trace("ContentWidth = "+imageWidth+"\nmouse X = "+mouseXPos+"\nmouse Y = "+mouseYPos);
if(MouseEvent.delta > 0){
if(zoomCounter > 0){
if(zoomCounter != 5){
//var moveRight = aSp2.horizontalScrollPosition;
//var moveDown = aSp2.verticalScrollPosition;
//moveRight = moveRight + aSp2.content.width/2;
//moveDown = moveDown + aSp2.content.height/2;
//var centerX;
//var centerY;
//trace("MoveRight = "+moveRight+"\nMoveDown = "+moveDown);
//trace("MaxWidthScroll = "+aSp2.maxHorizontalScrollPosition+"\nMaxHeightScroll = "+aSp2.maxVerticalScrollPosition);
//trace("HScroll = "+aSp2.horizontalScrollPosition);
//trace("Content X = "+aSp2.content.x);
aSp2.content.scaleX += 1.1;
aSp2.content.scaleY += 1.1;
//aSp2.horizontalScrollPosition = aSp2.horizontalScrollPosition + aSp2.horizontalScrollPosition/2 ;
//aSp2.verticalScrollPosition = aSp2.verticalScrollPosition + aSp2.verticalScrollPosition/2;
aSp2.update();
zoomCounter = zoomCounter - 1;
}
}
if(zoomCounter == 5){
aSp2.content.scaleX = 1;
aSp2.content.scaleY = 1;
aSp2.content.y = 0;
aSp2.update();
zoomCounter = 4;
}
trace("Zoom IN Count = "+zoomCounter);
}else{
if(zoomCounter == 4){
scaleAmt = 1920/aSp2.content.width;
aSp2.content.scaleX = scaleAmt;
aSp2.content.scaleY = scaleAmt;
aSp2.content.y = 520-(aSp2.content.height/2);
aSp2.update();
zoomCounter = 5;
}
if(zoomCounter < 4){
aSp2.content.scaleX -= 1.1;
aSp2.content.scaleY -= 1.1;
aSp2.update();
zoomCounter = zoomCounter + 1;
}
trace("Zoom OUT Count = "+zoomCounter);
}
}
The magic word you are looking for is "Pivot" (in AS3 is called "registration point").
But here we have the fastest way to do it:
You should put your image inside a container Sprite
function reposition():void{
var changex:Number=(container.x-zoomPt.x)/container.scaleX;
var changey:Number=(container.y-zoomPt.y)/container.scaleX;
container.x=zoomPt.x;
container.y=zoomPt.y;
for(var i:int=0;i<container.numChildren;i++){
container.getChildAt(i).x+=changex;
container.getChildAt(i).y+=changey;
}
}
The complete guide is here

Previous function is called/Event listener 'delayed' by one

I have altered an image carousel I downloaded, to rotate to the next picture (left or right) when I click one of the two buttons I added. The original carousel was built to rotate according to mouse movement/position.
For some reason, whenever I click the 'right' or 'left' button, which each rotate the carousel in their respecive directions, the event listener/handler is one 'behind'. It does whatever it should've done the previous time I clicked a button. To put it more clearly, the first button click it does nothing. The second button click it responds to what I clicked the last time.
Example:
I click the left button, nothing happens.
Then I click the right button, the carousel rotates to the left (because I clicked the left button before this click)
Then I click the left button, the carousel rotates to the right (idem).
See the code below. It seems fairly simple; no complex structure or whatever.
You can ignore most vars and positioning (like focalLength,vanishingPointX,radius, etc), I suppose. I'm guessing this bug is either related to the importing/processing of the XML, the for() loops, or the structure the .as file has.
package {
//here are all the imports
public class Imagereel extends Sprite {
var imgurl:URLRequest = new URLRequest()
var loadedimgs:uint = 0;
var images_num = 0;
var imageHolders:Array = new Array();
var imageHolder:MovieClip;
var btnLeft:BtnLeft = new BtnLeft;
var btnRight:BtnRight = new BtnRight;
//Set the focal length
var focalLength:Number = 2000;
//Set the vanishing point
var vanishingPointX:Number = stage.stageWidth / 2;
var vanishingPointY:Number = stage.stageHeight / 2;
//The 3D floor for the images
var floor:Number = 40;
//Radius of the circle
var radius:Number = 350;
//We use 70x70 sized images (change this if different for your images)
const IMAGE_WIDTH:uint = 393;
const IMAGE_HEIGHT:uint = 249;
var xmlLoader:URLLoader = new URLLoader();
var xmlData:XML = new XML();
public function Imagereel() {
//here's the positioning of the buttons
//here are the button addChilds
xmlLoader.load(new URLRequest("carousel.xml"));
xmlLoader.addEventListener(Event.COMPLETE, LoadXML);
btnLeft.addEventListener(MouseEvent.CLICK, prevImg);
btnRight.addEventListener(MouseEvent.CLICK, nextImg);
}
function LoadXML(e:Event):void {
xmlData = new XML(e.target.data);
Parseimage(xmlData);
}
function Parseimage(imageinput:XML):void {
var imageurl:XMLList = imageinput.image.iurl;
images_num = imageurl.length();
for (var i:int = 0; i < images_num; i++) {
var urlElement:XML = imageurl[i];
imageHolder = new MovieClip();
var imageLoader = new Loader();
imageHolder.addChild(imageLoader);
imageHolder.mouseChildren = false;
imageLoader.x = - (IMAGE_WIDTH);
imageLoader.y = - (IMAGE_HEIGHT);
imageHolders.push(imageHolder);
imgurl.url = imageurl[i];
imageLoader.load(imgurl);
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoaded);
}
}
function imageLoaded(e:Event) {
//Update the number of loaded images
loadedimgs++;
//Check to see if this is the last image loaded
if (loadedimgs == images_num) {
//Set up the carousel
initializeCarousel();
}
}
function initializeCarousel() {
//Calculate the angle difference between the images (in radians)
var angleDifference:Number = Math.PI * (360 / images_num) / 180;
//Loop through the images
for (var i:uint = 0; i < imageHolders.length; i++) {
//Assign the imageHolder to a local variable
var imageHolder:MovieClip = (MovieClip)(imageHolders[i]);
//Get the angle for the image (we space the images evenly)
var startingAngle:Number = angleDifference * i -0.30;
//Position the imageHolder
imageHolder.xpos3D = radius * Math.cos(startingAngle);
imageHolder.zpos3D = radius * Math.sin(startingAngle);
imageHolder.ypos3D = floor;
//Set a "currentAngle" attribute for the imageHolder
imageHolder.currentAngle = startingAngle;
var scaleRatio = focalLength/(focalLength + imageHolder.zpos3D);
//Position the imageHolder to the stage (from 3D to 2D coordinates)
imageHolder.x = vanishingPointX + imageHolder.xpos3D * scaleRatio;
imageHolder.y = vanishingPointY + imageHolder.ypos3D * scaleRatio;
//Add the imageHolder to the stage
addChild(imageHolder);
}
sortZ();
}
function prevImg(e:MouseEvent) {
//Loop through the images
for (var i:uint = 0; i < imageHolders.length; i++) {
var imageHolder:MovieClip = (MovieClip)(imageHolders[i]);
//Set a new 3D position for the imageHolder
imageHolder.xpos3D = radius * Math.cos(imageHolder.currentAngle);
imageHolder.zpos3D = radius * Math.sin(imageHolder.currentAngle);
var scaleRatio;
//Calculate a scale ratio
scaleRatio = focalLength/(focalLength + imageHolder.zpos3D);
//Update the imageHolder's coordinates
imageHolder.x = vanishingPointX+imageHolder.xpos3D * scaleRatio;
imageHolder.y = vanishingPointY+imageHolder.ypos3D * scaleRatio;
//spinning the carousel
imageHolder.currentAngle += 0.6285;
}
//Call the function that sorts the images so they overlap each others correctly
sortZ();
}
function nextImg(e:MouseEvent) {
//Loop through the images
for (var i:uint = 0; i < imageHolders.length; i++) {
var imageHolder:MovieClip = (MovieClip)(imageHolders[i]);
//Set a new 3D position for the imageHolder
imageHolder.xpos3D = radius * Math.cos(imageHolder.currentAngle);
imageHolder.zpos3D = radius * Math.sin(imageHolder.currentAngle);
var scaleRatio;
//Update the imageHolder's coordinates
imageHolder.x = vanishingPointX+imageHolder.xpos3D * scaleRatio;
imageHolder.y = vanishingPointY+imageHolder.ypos3D * scaleRatio;
//spinning the carousel
imageHolder.currentAngle -= 0.6285;
}
sortZ();
}
//This function sorts the images so they overlap each others correctly
function sortZ():void {
imageHolders.sortOn("zpos3D", Array.NUMERIC | Array.DESCENDING);
//Set new child indexes for the images
for (var i:uint = 0; i < imageHolders.length; i++) {
setChildIndex(imageHolders[i], i);
}
}
}
}
So what this code does:
carousel.xml is imported
The xml is processed so that the image paths there are converted to displayed images
A carousel is made out of the images
The sortZ() function makes sure that the images are aligned in 3D perspective properly; just like z-index in CSS would do.
When clicking btnLeft or btnRight, the carousel rotates to the left or right (this is done by updating the value of imageHolder.currentAngle).
When I put trace's inside the prevImg() and nextImg() functions, I do see the trace that
belongs to the right function, and not the previously clicked one. So it seems that Flash does call the right event.
So how do I get rid of this bug?
Help and tips are greatly appreciated!
Move imageHolder.currentAngle assignment (the line where you change it) BEFORE the code that alters imageHolder's 3D position.
for (var i:uint = 0; i < imageHolders.length; i++) {
var imageHolder:MovieClip = (MovieClip)(imageHolders[i]);
//Set a new 3D position for the imageHolder
imageHolder.currentAngle += 0.6285; // <== HERE
imageHolder.xpos3D = radius * Math.cos(imageHolder.currentAngle);
imageHolder.zpos3D = radius * Math.sin(imageHolder.currentAngle);
var scaleRatio;
//Calculate a scale ratio
scaleRatio = focalLength/(focalLength + imageHolder.zpos3D);
//Update the imageHolder's coordinates
imageHolder.x = vanishingPointX+imageHolder.xpos3D * scaleRatio;
imageHolder.y = vanishingPointY+imageHolder.ypos3D * scaleRatio;
//spinning the carousel
}
The same for the other function.

Moving movieclips across the stage on FrameEnter

I'm making an image gallery and I want to have a bunch of thumbnails on the bottom of the screen that smoothly slide from side to side when the mouse moves.
I'm working with a custom class for the container (Tiles) and a custom class for the thumbnails (ImageIcon).
I have a ComboBox which allows users to to choose a gallery. When the user chooses a gallery, the following code is run and the thumbnails should reload. The problem here is that the icons appear on top of each other instead of side by side, also switching categories should remove the old one (see the first for loop), but it does not. Also, the Icons are not animating properly. The animation code is below as well. Right now, only one of the icons will move. The icons should move in order from side to side, stopping when the last few icons hit the edge of the screen, so that they don't get "lost" somewhere off to the side.
Gallery Loader Code:
public function loadCategory(xml:XML = null) {
if (xml != null) {
dp = new DataProvider(xml);
for (var k:int = 0; k < this.numChildren; k++) {
this.removeChild(this.getChildAt(k));
}
var black:DropShadowFilter = new DropShadowFilter(0, 45, 0x000000, 1, 3, 3, 1, 1);
var white:DropShadowFilter = new DropShadowFilter(0, 45, 0xFFFFFF, 1, 2, 2, 1, 1);
for (var i:int = 0; i < dp.length; i++) {
var imgicon:ImageIcon = new ImageIcon();
imgicon.addEventListener(MouseEvent.CLICK, showImage);
imgicon.width = 100;
imgicon.x = (i * (imgicon.width + 20));
imgicon.path = dp.getItemAt(i).data;
imgicon.loadIcon();
imgicon.filters = [black, white];
stage.addEventListener(Event.ENTER_FRAME, moveIcon);
this.addChild(imgicon);
}
} else {
//Failed to load XML
}
}
Icon Animation Code:
public function moveIcon(e:Event){
var speed = 0;
speed = Math.floor(Math.abs(this.mouseX/20));
var image = this.getChildAt(k);
var imagebox = image.width + 20;
var edge:Number = (800/2);
if (this.mouseX > 0) {
for (var k:int = 0; k < this.numChildren; k++) {
if (image.x - (imagebox/2) + speed < -edge + (k * imagebox)) {
speed = 0;
}
image.rotationY = Math.floor(image.x/20);
image.x -= Math.floor(speed);
}
} else {
for (var j = this.numChildren; j >= 0; j--) {
if (image.x + speed > edge - ((imagebox * j) )) {
speed = 0;
}
image.rotationY = Math.floor(image.x/20);
image.x += Math.floor(speed);
}
}
}
As I see it, you have three questions (You should have put these at the end of your question instead of "What is wrong with my code?"). One of the main principles of programming is breaking problems into smaller parts.
How do I line up the ImageIcon beside each other?
How do I remove the old ImageIcon, when switching categories?
How do I animate ALL the ImageIcons together, based on the mouse position, with constraints on either side?
Question 1
I can't see anything wrong, but just check that when you are setting imgicon.x, that imgicon.width is actually set.
Question 2
Instead of relying on numChildren and getChildAt(), I would create a currentIcons array member variable, and when you create a new ImageIcon, just push it onto the array. Then when you want to remove them, you can just loop through the array like this:
for each (var cIcon:ImageIcon in currentIcons)
{
cIcon.removeEventListener(MouseEvent.CLICK, showImage);
removeChild(cIcon);
}
currentIcons = [];
As you can see, I am also removing any listeners that I have added. This is best practice. Then clearing the array when I have removed all the icons.
Question 3
I can see a few things wrong with your code. First, in the line where image is set, k is not set yet!
Here you can also use the currentIcons array, but you probably can't use a for each in loop, because that gives you the items out of order. Just a normal for loop will be better.
I haven't tested this code for the moveIcon method, but the idea should work. You may have to tweek it though:
public function moveIcon(e:Event):void
{
var speed:Number = Math.floor(this.mouseX / 20); // Removed "abs".
var imageBox:Number = currentIcons[0].width;
var edge:Number = 800 / 2;
for (var i:int = 0; i < currentIcons.length; i++)
{
var image:ImageIcon = currentIcons[i] as ImageIcon;
image.x += speed;
image.rotationY = Math.floor(image.x / 20);
var min:int = -edge + (i * imagebox);
if (image.x < min) image.x = min;
var max:int = edge - (imagebox * i);
if (image.x > max) image.x = max;
}
}
EDIT* Sorry, it was supposed to be a greater than in the last if statement, but I had a less than by accident.

How to modify position of children inside loop in AS3

I'm trying to make a dynamic image gallery from and xml. From my tutorials, right now i've got it so it will constantly add the next thumbnail below the other, which is fine, but I'm trying to figure out how to make it that once it reaches a certain y coordinate, it will move the x coordinate over and stack them again. So that rather one long list of thumbs, it will be a side by side stack. For some reason, I can't get it in my head how something like this would work. My goal is to have a side by side stack that I will end up putting in a movie clip that will be masked to show only 2 stacks at a time. Then when clicking a button will slide it over. I was planning to use the "movieclip.length" to calculate how far over to move it, but i haven't gotten that far yet. This is what I got:
var gallery_xml:XML;
var xmlReq:URLRequest = new URLRequest("xml/content.xml");
var xmlLoader:URLLoader = new URLLoader();
var imageLoader:Loader;
function xmlLoaded(event:Event):void
{
gallery_xml = new XML(xmlLoader.data);
info_txt.text = gallery_xml.screenshots.image[0].attribute("thumb");
for(var i:int = 0; i < gallery_xml.screenshots.image.length(); i++)
{
imageLoader = new Loader();
imageLoader.load(new URLRequest(gallery_xml.screenshots.image[i].attribute("thumb")));
imageLoader.x = 0;
imageLoader.y = i * 70 + 25;
imageLoader.name = gallery_xml.screenshots.image[i].attribute("src");
addChild(imageLoader);
imageLoader.addEventListener(MouseEvent.CLICK, showPicture);
}
}
xmlLoader.load(xmlReq);
xmlLoader.addEventListener(Event.COMPLETE, xmlLoaded);
function showPicture(event:MouseEvent):void
{
imageLoader = new Loader();
imageLoader.load(new URLRequest(event.target.name));
imageLoader.x = 200;
imageLoader.y = 25;
addChild(imageLoader);
}
I can't seem to wrap my head around what I can do to move things over dynamically without having to write a custom if for each set of positions.. I get the feeling I've totally forgotten how to do algebra.
I'd suggest one of the following two solutions (untested):
var next_x:Number = 0.0;
var next_y:Number = 25.0;
for (var i:int = 0; i < gallery_xml.screenshots.image.length(); ++i)
{
// ...
imageLoader.y = next_y;
imageLoader.x = next_x;
next_y += 70;
if (next_y > THRESHOLD)
{
next_x += X_OFFSET;
next_y = 25.0;
}
}
That is, just keep track of where your next image will be placed and adjust that coordinate accordingly when you exceed thresholds. If you want to use your i variable, you'll have to calculate what row and column the image will be placed at:
for (var i:int = 0; i < gallery_xml.screenshots.image.length(); ++i)
{
// ...
var row:int = i % MAX_ITEMS_PER_COLUMN;
var column:int = i / MAX_ITEMS_PER_COLUMN;
imageLoader.y = 25 + row * 70;
imageLoader.x = column * COLUMN_WIDTH;
}