WynApse Home Page
Home    Blog    About    Contact       
Latest Article:


My Tags:
My Sponsors:






Lifetime Member:

Silverlight createFromXaml Object Creation/Destruction





Beta 1 Conversion

There wasn't much to the conversion on this beyond what's been discussed in other articles. The basic plumbing, the changes to getting the control and the control call for createFromXaml are the main ones. No fancy setup with Load= to cause problems either!

Silverlight Concepts Covered

  • createFromXaml
  • getItem
  • remove
  • clear
My GlyphMap Utility grew out of control once I started it, and although it covered a lot of topics, it really didn't lend itself to any in-depth discussion of anything. This is the first discussion of any of the topics contained in that application. createFromXaml is used so much in the GlyphMap Utility that I decided I should cover it first.

createFromXaml

In the WPF/E SDK, every object has a syntax definition that includes XAML and Scripting. Next to scripting, it always reads "To create an object using scripting, see CreateFromXaml.

So what does that really mean?

createFromXaml is part of a process by which you define an object in a string in JavaScript identical to how it would look in xaml, then create an object from the xaml string, and finally attach that object to the canvas of choice.

An example would help:

var xamlFragment = '<TextBlock FontSize="14" 
Canvas.Top="25"
Canvas.Left="20"
Foreground="Black"
Text="Hello XAML" />';



This string is identical to one that would be declared in a xaml file, and should be familiar. The only difference here is that it is defined as a JavaScript variable, and surrounded by single quotes.

The next step is to create the object, and to do so, we need a reference to our Silverlight control.

Note this code fragment has been updated to reflect the Beta 1 syntax for getting the control variable, and the addition of "content" to the createFromXaml call:

var control = sender.getHost();var TextBlock = control.content.createFromXaml(xamlFragment);


control.createFromXaml takes our string definition and produces an object. Note the var TextBlock could be any name in this case, but I find it easier to name it as the type I'm creating if I know it, to avoid confusion later.

Once the object is created, all that remains is to attach it to the canvas somewhere:



someCanvas.children.add(TextBlock);



In this example, the TextBlock would be added to the canvas object previously identified as "someCanvas". someCanvas can be any canvas object... it doesn't have to be the 'main' or outer canvas under control.

In the project on this page, I defined a secondary (empty) canvas as "MainCanvas" in the xaml file below the 3 hyperlinks. mainCanvas is then defined as a variable pointing at "MainCanvas", so the objects will be added below the three hyperlinks on the page. That also answers why the hyperlinks are not involved in any of the other manipulations.

Listing Objects

To help clarify some of the concepts, I decided to have a piece of code that enumerates all the objects in the "MainCanvas". mainCanvs.children.count returns the number of 0-based objects in the canvas. Since in this example, we're only adding TextBlock and Rectangle objects, we are fairly certain that's all we're going to find, but I added a default "Unknown" for completeness.

getItem

As we're indexing through the objects in the canvas, we can find out information about the object at an index by using getItem. getItem returns the object at an index:

var child = mainCanvas.children.getItem(i);

Unless you're operating under very strict conditions, you won't know what the object is without getting more information. The object type can be found:

child.ToString()

This will return the object name such as "TextBlock" or "Rectangle". The btnListIt code uses the object type in a switch case to then request more information from the child object to build up a xaml-like output line for each object in the canvas.

Operation of Example

To demonstrate createFromXaml, the "Fill Text" hyperlink adds 15 rows by 4 columns of TextBlock objects into the output canvas. Pressing "List" will then display those back in the output box. Note that the order returned is exactly the order created: left-to-right, top-to-bottom as expected.

To make the demonstration more interesting, I added a "Boxes" hyperlink that causes code to be executed to place a Rectangle object around each TextBlock object using the TextBlock object's returned coordinates in the xamlFragment for the Rectangle.

Pressing "List" after boxing the text will show that although the rectangles are surrounding the TextBlocks, the objects are all after the TextBlocks since they were added to the canvas after the fact.

When "Fill Text" completes, the link changes to "Empty Text". If "Empty Text" is pressed after the boxes are on the canvas, the text is removed leaving only the Rectangles, and "List" then shows the Rectangles with a 0-based index because they are the only objects remaining. This shuffling of the index is an important point to remember when manipulating objects on the canvas at run-time as will be brought up again later.

As with "Fill Text", the "Boxes" link changes to "No Boxes" upon completion, and removes the Rectangle objects when pressed.

remove

Removing objects is a simple manner if you know the name or index of the object and the canvas to which it is attached. If the child object's name is "ObjectName", and it is on mainCanvas, for example:

var child = sender.findName("ObjectName");mainCanvas.children.remove(child);


In the GlyphMap Utility, I named each Glyph with it's Indice to make it easy to find and reference. In this example, I didn't name the objects because I had no intention of individually manipulating them. To remove all the TextBlocks, I simply:

// Have to get a count first and decrement
var nChildCount = mainCanvas.children.count;
for (i = nChildCount - 1; i >= 0; i--)
{
var child = mainCanvas.children.getItem(i);
// We're only removing the TextBlock objects
if (child.ToString() == "TextBlock")
{
mainCanvas.children.remove(child);
}
}


The only tricky part of this piece of code is that the child items need to be indexed in reverse order. Remember the re-shuffling of indices mentioned earlier... if I start at index 0, I will remove child 0. Before I get a chance to remove child 1, however, the canvas elements are reshuffled and what had been child 1 is now child 0, et. So when I remove child 1, I am actually skipping one, and the skip would continue leaving every other TextBlock still on the screen.

To avoid that problem, I get a count of the objects outside the loop, then my for loop decrements through the list of objects.

clear

If you want to simply remove everything from the canvas, this code can be executed:

mainCanvas.children.clear();


And that is exactly what the "Clear" hyperlink does.

Conclusion

Armed with the information in this article, it would be instructive to review the JavaScript in the GlyphMap Utility. createFromXaml, getItem, remove, and clear were used extensively in that application. When the map was constructed, a xamlFragment was created for each Glyph index from 0 to 299, in addition to the numerical indices along the top and left borders. When the font was changed, the canvas was cleared and the process restarted.

Coded up in the xamlFragment for each Glyph was code to produce the tooltip for the Glyph. The tooltip is nothing more than a xaml object that is created and added to the canvas and then removed later. getItem was used to figure out which Glyph was receiving the rollover messages to put up tooltips appropriately.

JavaScript for producing this page:

<script>
function btnTextFill(sender, args) {
var control = sender.getHost();

// Save the main canvas for use in createFromXaml code
mainCanvas=sender.findName("MainCanvas");

switch (sender.findName("TextFill").Text)
{
case "Fill Text":
// Add 15 rows by 4 columns of text
for (nRowIndex=0; nRowIndex < 15; nRowIndex++)
{
for (nColIndex=0; nColIndex < 4; nColIndex++)
{
// Define a xaml fragment for the a text block,
// 20 pixels from the left, and every 120 pixels for 4 columns,
// 25 pixels from the top and every 22 down for 15 rows
var xamlFragment = '<TextBlock FontSize="14" Canvas.Top="' + (nRowIndex*22 + 25) + '" Canvas.Left="' + ((nColIndex)*120+20) + '" Foreground="Black" Text="createFromXaml" />';
var TextBlock = control.content.createFromXaml(xamlFragment);

// Add the xaml fragment as a child of mainCanvas
mainCanvas.children.add(TextBlock);
}
}

// Setup to remove it if hit again
sender.findName("TextFill").Text="Empty Text";
break;

case "Empty Text":
// Have to get a count first and decrement
var nChildCount = mainCanvas.children.count;
for (i = nChildCount - 1; i >= 0; i--)
{
var child = mainCanvas.children.getItem(i);
// We're only removing the TextBlock objects
if (child.ToString() == "TextBlock")
{
mainCanvas.children.remove(child);
}
}

// Setup to refill
sender.findName("TextFill").Text="Fill Text";
break;
}

}

function btnBoxIt(sender, args) {
var control = sender.getHost();

// Save the main canvas for use in createFromXaml code
mainCanvas=sender.findName("MainCanvas");

switch (sender.findName("BoxIt").Text)
{
case "Boxes":
// get a count of how many children there are
// OUTSIDE the loop
var nCount = mainCanvas.children.count;
for (i = 0; i < nCount; i++) {
// We should only have TextBlock objects
// but for completeness, check for it
var child = mainCanvas.children.getItem(i);
if (child.ToString() == "TextBlock")
{
// Add a Rectangle for each object,
// using the object's top and left values
var xamlFragment = '<Rectangle Canvas.Top="' + (child["Canvas.Top"] + 2) + '" Canvas.Left="' + (child["Canvas.Left"]-2) + '" Width="115" Height="16" Stroke="SteelBlue" />';
var RectBlock = control.content.createFromXaml(xamlFragment);
mainCanvas.children.add(RectBlock);
}
}

// Setup to remove the boxes
sender.findName("BoxIt").Text="No Boxes";
break;

case "No Boxes":
// Get a count OUTSIDE the loop
var nChildCount = mainCanvas.children.count;
for (i = nChildCount - 1; i >= 0; i--)
{
// Remove the child object if it is a Rectangle
var child = mainCanvas.children.getItem(i);
if (child.ToString() == "Rectangle")
{
mainCanvas.children.remove(child);
}
}

// Setup for re-boxing the text
sender.findName("BoxIt").Text="Boxes";
break;
}
}

function btnListIt(sender, args) {
var control = sender.getHost();

// Save the main canvas for use in createFromXaml code
mainCanvas=sender.findName("MainCanvas");

// Enumerate the children of the Canvas object.
var sOutput = "Canvas Is Empty";
if (mainCanvas.children.count > 0)
{
sOutput = "Canvas Contains " + mainCanvas.children.count + " child objects\n";
for (i = 0; i < mainCanvas.children.count; i++) {
var child = mainCanvas.children.getItem(i);

// We know we only have the two kinds of objects,
// But for completeness, list any unknowns
switch (child.ToString())
{
case "TextBlock":
var sOutput=sOutput + "Child Item " + i + ": <TextBlock FontSize=\"" + child.FontSize + "\" Canvas.Left=\"" + child["Canvas.Left"] + " Canvas.Top=\"" + child["Canvas.Top"] + "\" />\n";
break;

case "Rectangle":
var sOutput = sOutput + "Child Item " + i + ": <Rectangle Canvas.Left=\"" + child["Canvas.Left"] + " Canvas.Top=\"" + child["Canvas.Top"] + "\" Width=\"" + child.Width + " Height=\"" + child.Height + "\" />\n";
break;

default:
var sOutput = sOutput + "Child Item " + i + ": Unknown\n";
break;
}
}
}
window.document.aspnetForm.Output.value=sOutput;
}

function btnClearIt(sender, args) {
mainCanvas=sender.findName("MainCanvas");
mainCanvas.children.clear();
sender.findName("TextFill").Text="Fill Text";
sender.findName("BoxIt").Text="Boxes";
}

function RollIntoLink(sender, args) {
sender.ForeGround="Blue";
}

function RollOutofLink(sender, args) {
sender.ForeGround="Red";
}


</script>


XAML for producing this page:

<Canvas 
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<TextBlock x:Name="TextFill"
FontSize="14"
Canvas.Top="0"
Canvas.Left="0"
Foreground="Red"
FontWeight="900"
Text="Fill Text"
MouseLeftButtonDown="btnTextFill"
Cursor="Hand"
MouseEnter="RollIntoLink"
MouseLeave="RollOutofLink"/>

<TextBlock x:Name="BoxIt"
FontSize="14"
Canvas.Top="0"
Canvas.Left="90"
Foreground="Red"
FontWeight="900"
Text="Boxes"
MouseLeftButtonDown="btnBoxIt"
Cursor="Hand"
MouseEnter="RollIntoLink"
MouseLeave="RollOutofLink"/>

<TextBlock FontSize="14"
Canvas.Top="0"
Canvas.Left="170"
Foreground="Red"
FontWeight="900"
Text="List"
MouseLeftButtonDown="btnListIt"
Cursor="Hand" MouseEnter="RollIntoLink"
MouseLeave="RollOutofLink"/>

<TextBlock FontSize="14"
Canvas.Top="0"
Canvas.Left="230"
Foreground="Red"
FontWeight="900"
Text="Clear"
MouseLeftButtonDown="btnClearIt"
Cursor="Hand" MouseEnter="RollIntoLink"
MouseLeave="RollOutofLink"/>

<Canvas x:Name="MainCanvas" />


</Canvas>
Copyright © 2006-2017, WynApse