Damon Payne: Hand waving Silverlight Architect

103db signal to noise ratio at < .03% total harmonic distortion
Solution Architect, software developer, geek
Damon Payne at Blogged
2009 Microsoft MVP - Client App Dev
2007 Microsoft MVP - Solution Architecture
 Thursday, October 30, 2008

The AGT (Argentum Tela) series of articles is an effort to do two things.  Usually an idea is presented only in its finished form.  The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches.  The second goal, and the example vehicle for the evolution aspect, is an extensible Design Surface for Silverlight similar to what we have in Visual Studio 2008.   This type of application has all sorts of interesting uses.  My example is a Home Theater layout tool.  Read the entire saga: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx

Choosing an Approach to Editing Properties

One of the last top level pieces to begin fleshing out is Property Editing.  I don’t want to see “Red Couch” in the toolbox anymore: I want to see “Couch” and to change its color at runtime by editing his properties.  Having had a lot of experience writing things like this so as to fit neatly within the framework Microsoft gives us, I felt very familiar with the benefits and drawbacks of these approaches.  I had to ask myself some questions:

1.       Is the PropertyGrid really the best way to go about changing attributes of objects on a design surface?

2.       Is a Visual Studio style grid the best route?  Maybe a smaller gird that appears right next to a Component (ala Visio) is better?  Maybe there should be more interaction with the Component itself?

3.       How important is it to support the editing of a Property for many Components at once?

4.       How important is Undo?  Redo?

5.       What is the best contract between IDesignableControl and “the design time environment” for determining which properties are editable and how they should be edited?

When writing custom controls for the Visual Studio designer, it always bothered me that the Contract between my Controls and the Designer Time Environment was simply some attributes on public properties.  Now that I’m in a position to change it for my own world, I’m having trouble thinking of something better.  My ideas on what is the most “correct” in terms of Object- and Component- oriented semantics is not the only thing at play here.  As a component creator, what do I want my software development experience to like?  Is placing eight attributes on a property the easiest and most sensible way?  If I diverge from what’s present in the Visual Studio design time environment, how much extra work am I creating?

What design-time support is present in Silverlight anyway?

Designer Support native to Silverlight

What namespaces, classes, interfaces, and attributes we are accustomed to from “full framework” design time support have made it into Silverlight?

System.Web.UI.Design.SilverlightControls

This namespace only contains things related to a Silverlight plug-in control on ASPX pages.  There’s nothing useful for us here.

System.ComponentModel

Many of the familiar friends are here:

·         CategoryAttribute – Place this on a property indicating what category the property belongs to in a categorized PropertyGrid.

·         DefaultValueAttribute – specify the default value for a property

·         DescriptionAttribute- a Description for a property or event

·         DesignerProperties class.  This one is interesting.  At the time I started this article, RTW was not quite out and the documentation on MSDN didn’t really say anything about what this actually did.  The description now says “The DesignerProperties class provides attached properties that can be used to query the state of a control when it is running in a designer. Designer tools will set values for properties on objects that are running in the designer.” Ultimately, though, this is an attached property for “IsInDesignMode”, which I don’t know if I’ll need or not.

·         EditorBrowsableAttribute – In what situations should this property be shown by designers or intellisense

·         TypeConverter – TypeConverter is missing some things from the full framework. The MSDN documentation still says it supports “standard values” but those methods (to return a standard values collection) are missing in Silverlight.

·         TypeConverterAttribute –present and accounted for!

Missing in action:

·         System.ComponentModel.TypeDescriptor – In the full framework, TypeDescriptor is extremely important to the design-time environment.  TypeDescriptor is used to augment Reflection to specify what attributes a Component has. 

o   TypeDescriptor tells us what kind of Editor is used to modify an instance of a Component

o   TypeDescriptor tells us what kind of Designer a particular component uses

o   TypeDescriptor is used to create design-time instances of a type

·         TypeDescriptor is extensible via IExtenderProvider, ITypeDescriptorFilterService, ICustomTypeDescriptor, all missing.

·         System.ComponentModel.Editor

·         System.Drawing.UITypeEditor

In some cases I’ve already made design decisions that negate the need for some of these missing things.  Other’s I’ll have to introduce or replace.

Design Goals

Property editing is complex.  Now that I see the lay of the land, I can state some design goals and some approaches to achieving them.

1.       The items on the design surface (IDesignableControl) need to be able to be inspected for a list of Properties that are displayable and editable.  It should be painless for Control authors to do this.

2.       When a property is found to be displayable, we must be able to determine what type of visual Control should be used to display the property value.

3.       When a property is found to be editable, we must be able to determine:

a.       The type of the property

b.      The category of the property

c.       What type of visual Control should be used to edit the property value

4.       We should be able to display all of this in a PropertyGrid-esque fashion.

In order to reach these goals, I decide on the following:

1.       I’m going to try replacing the attribute based Component-Designer contract mechanism with new and modified interfaces.

2.       I’m going to make the Component inspection mechanism pluggable, in case someone really wants to fall back on Reflection.

3.       I’m going to come up with some better named interfaces and types to use to modify property values.

4.       I’m going to build some helpers/extension methods to make this painless for IDesignableControl authors.

5.       I’m going to build a PropertyGrid from scratch.

Working on these ideas, I have come up with this:

Since no code was written as part of this article, the most updated code is still AGT[14].

In the next article we’ll look into implementing this approach.



Thursday, October 30, 2008 1:40:30 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, October 29, 2008

I love the auto generated Google ads:

AtheistPrices

I didn't know the 13th Amendment had been repealed?



Wednesday, October 29, 2008 2:43:22 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

I really will get back to technical blogging tomorrow, but politics are consuming too many mental cycles right now.

As I said here, one monumental failure of the current political "share the wealth" thinking is that they simply seek to avoid reality.  We cannot pretend that there are no consequences to taxing wealth.

From this article on Corey the well driller, it would seem that the current political Left feels that people who "fall through the cracks" because they didn't have health care are of utmost national concern.  People like Corey who finally turn a profit after 30 years of struggle and "fall through the other cracks" are not their concern.  After all there are precious few people in America like Corey the well driller and while we as a nation need them, politicians don't need their votes.

As I have said often enough in the past, our notion of the "wealthy" that we "need to tax more" is astoundingly unhinged from reality to begin with.  In addition to my previous thoughts on how horribly dishonest it is to say that Obama's plan will only tax those making more than $250,000 per year, there are some other concerning aspects of this mentality.

First, the plan is a broad stroke.  Even if one bought into the horribly flawed idea that we should tax these "wealthy" people we have used a poor metric with which to delineate them.  In, say, Missouri, $250,000 is a fortune because the cost structure there is cheap compared to other areas of the country.  In Wisconsin, $250,000 doesn't go quite as far but is still a very nice income.  In Manhattan where the cost of living is something like 400% higher than in Milwaukee, a salary of $250,000 may very well include plain old "working middle class professionals". 

The goals and methods of the would-be wealth re-distributors can be even more suspect (if such is possible) using another mathematical method of evaluation.  Let us suppose that the range of income in the United States is from $Zero to $5billion per year.  We have defined the "wealthy", the point at which we should take an extra pound of flesh from people, as $250,000/year.  $250,000 is .005% of $5billion.  If I'm making $250,000 per year and in the same tax bracket, in the same "Abused, eat the rich, they've got plenty" demographic as people making 20,000 times what I make what does that tell me about the nation?  If I'm middle management at an insurance company do I feel that those economic powerhouses are my Peers?  I'm in the same class as Warren Buffet and Bill Gates?  Really?

We have not only demonized Wealth in this nation, we have hijacked the term to mean "anyone making more than I expect I ever will".  Many employees no doubt consider their direct supervisors a member of this privileged upper crust, people who at best make perhaps 30% to 50% more than they do and are already being punished for it via higher marginal tax rates and loss of various categories of deductions. 

This populist class warfare goes hand-in-hand with another phenomenon unfolding over the past decades in America.  When Americans observe a candidate who is well educated, articulate, intelligent, and cultured, we call them an Elitist.  We say that it's bad to be an Elitist.  We say that the other candidate who has few or none of these desirable qualities is "more like me", "down to earth", "someone I could have a beer with", and we want them to call the shots instead of the Elitist.

We exalt the common and seek to elevate the mediocre to the level of the Good.  We have declared, as a nation, that those who Do have fewer rights than those who Can't or Won't.   We seek to punish our betters precisely because of their qualities that we ourselves have identified as Good and Admirable.

America is currently engaged, on all possible fronts, in a War on Excellence.



Wednesday, October 29, 2008 9:20:51 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Tuesday, October 28, 2008

I read Obama's "Blueprint for change" plan from his website, all 64 pages of it.  I am not reading McCain's plan because at this point I highly doubt he'll be elected.  Regular readers know that I don't have a horse in this race, being neither a McCain nor Obama supporter.

My first observation about Obama's plan is that he seems to have everyone covered here, at least everyone who matters to someone who's most likely about to ride a populist vote into office.  Every special interest group has a message for them.  He has something to say to women, minorities, the poor, immigrants, the Green movement, the Labor Unions, people without Health Insurance,  people who can't survive on social security, veterans, people who can't pay their mortgages, the "middle class", people who want to go to college but can't, single parents, and farmers. 

Suspiciously missing is any statement to business owners small and large, to the people in this country legally, to Doctors and Nurses, to the people who are paying their mortgage, to the people saving for their own retirement, to the people who have health insurance, the people who did pay (and are still paying) for their higher education, to the people who saved, to the people who didn't make bad decisions during the housing bubble, to the people without credit card balances.  These people don't count.  These people can not be appealed to give up more of their freedom and offer more tax dollars to the government for the illusion of security.  These people are less likely to have usable sympathy for their less responsible fellow citizens.   No doubt we can expect these people to be sacrificed for the sake of providing for everyone in the first group.  But, wait, says Obama:  Any family making less than $250,000 per year can expect that their taxes won't go up!  Well, I've seen that said in the debates and such, but his plan has some sneaky ways around that fact.

  • Currently we only pay social security taxes on income up to $97,000.  Obama's plan calls for removing this cap to help meet the obligations of Social Security.  So, if you make more than $97,000, you and your employer can count on a 13% tax hike on that income.  Obama is pretending this tax isn't a tax?
  • Employers who can't or won't provide health care for their employees will be taxed (via payroll) so that their employees can participate in his Federal plan.  Some businesses may just absorb this new cost.  Some may not be able to and will pass it on to their customers, or from their employees in the form of smaller raises; some will have to spend less money elsewhere and hire fewer employees.  Lacking specifics on what "qualifying" businesses are, the plan seems to operate on the assumption that business owners are Fat Cats who have plenty of margin in their business to do anything we pass a law forcing them to do.  What about the additional cost in time and money of all the paperwork required to meet these obligations?
  • Obama plans to raise the minimum wage.  Once again, he assumes that the money is just sitting there and needs to be spread around.  Business owners who can't or won't sacrifice their profits to accomplish this will simply hire fewer employees, spend less money rewarding the employees they already have, and pass the cost on to their customers.   We cannot simply declare the Mediocre the equivalent of the Good and walk away.

Like so many politicians, Obama is pretending he can make laws that will "spread the wealth" with no negative side effects.  We need only look at the chronic 12% unemployment rate in Worker's Paradise countries like Germany to gain insight as to where these policies will lead us.  Sure they have health care and 5-weeks of vacation, but the cost is a near-zero GDP growth for a decade and more than double the unemployment of our "broken" system here in America.  When we simply declare that people ought to behave a certain way without regard for Reality and Rights the historical result has always been bread lines and homes that aren't warm in the winter.

If I get time, I will post a more detailed analysis of the Obama plan, possibly before Election Day.



Tuesday, October 28, 2008 10:39:54 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Tuesday, October 21, 2008

When one is giving a technical presentation, issues around source code can be some of the more difficult to plan for.  Do you have the code done already?  Do you write the code on the spot?  If you write code on the spot do you have the code divided into various snippets you can instantly drop in, or do you really type everything?  Is it OK to use a tool like CodeRush or ReSharper that not everyone will be familiar with?  Do you need to be prepared to offer code in both C# and VB.Net?

There has been a "current" or "meme" going around the local community here regarding how the code used in demonstrations is structured.  Some people, it seems, are tired of seeing Database Driven examples or One-Tier samples for .NET topics.  Most commonly picked on is LINQ-to-X: I have heard multiple complaints that it is bad form to bust out "var query = from x in context select x;" from an ASP.Net code behind to show how LINQ works.  In a real application one would have data access classes, Presenters or Controllers, possibly extra business logic classes, and so forth.  The argument goes on that giving a talk and failing to use appropriate separation of concerns like this teaches bad habits to developers and makes the members of the .NET community look like we all implement the Simpleton Pattern.

David Palfery vented to me that after his recent User Group talk on some Silverlight concepts someone approached his boss and gave a reproach for not using more a appropriate separation of concerns in his code demos.  What's the deal with that?  If you can't raise your hand to ask about this, talk to the presenter after the show, or comment on their blog - you are a coward.  Whatever your lot in life, it seems to me to be the most basic of courtesies to extend to someone who's taken their time to hopefully help you grok something.  But, leaving etiquette aside, was this person more or less correct?

The answer is - it depends. 

Was this a talk about Design Patterns or Silverlight Concepts?  What are the chances that an argument over Model-View-Presenter vs. Model-View-ViewModel would erupt?  I'm of the opinion that there are some basic questions that should be asked by an audience member if these things are a concern. 

Q: Excuse me, could you say a few words about what shortcuts you might be taking when demonstrating this concept? 

That's not hard, and assuming the presenter is knowledgeable and experienced they should be able to quickly describe how the quick sample app they are showing you differs from the real-world applications they have probably written before giving a talk in front of an audience.  If a quick description isn't enough to satisfy an audience member, you can put it on the back burner for after the talk.  If my talk is on the ins and outs of Silverlight Data Binding I might rather not spend time explaining how my data access layer, my presenters, my Commands, and my View Models work.  I have to make an assumption here.  If I'm going to use good patterns in my demo code, I'm going to have to assume that most or all of the audience members have at least passing familiarity with said patterns.  If I'm wrong, I'm going to have to set aside part of my presentation to explain the patterns.  This might be very enriching indeed, but myself and my audience may not be as satisfied as if we'd more thoroughly covered the intended topic.  If I'm right and the audience does have a certain level of pattern knowledge, they'll be thinking themselves about what the appropriate abstractions might be.

There are definitely cases though, beyond just knowing your audience, where it is absolutely important to utilize some patterns and practice separation of concerns within your demo code. 

  1. If the nature of the design affects your overall message
  2. If demonstration of a proper design would help neophytes fall into the Pit of Success easier and not detract from time spent on the Core Concepts of your talk
  3. If the quick-and-dirty demonstration method will not work for common situations

The last one probably needs some explanation, and I'm going to pick on Entity Framework here.  I generally like the Entity Framework, but every single early example I saw was creating an easy to follow breadcrumb trail to the Pit of Failure.  Consider a fairly common ASP.Net web forms scenario:

  1. Get some data objects on page load
  2. Bind objects to web controls
  3. Objects are likely to be updated, throw them in the session
  4. On some event, take data out of the web form controls, merge with object data in session, push back down to the data access layer

There were tons of quick and dirty LINQ to Entities examples with the LINQ query against an EntityContext in some Page_Load event, code behind, or other inappropriate place.  These set you up for disappointment.  If you try to push these objects back down to the database (after a couple of postbacks) using the Entity Framework you're going to get an exception saying that these objects are already a tied to some other EntityContext which is now lost.  If you destroy the EntityContext to get around this, you're going to find that you lost change tracking for your entities and wonder why your updates are updating Zero objects every time.  If you Detach() your objects from the EntityContext, thinking you'll re-attach later, you may find that your Entity relationships have disappeared.  You probably don't want to keep an EntityContext open for the lifetime of a Use Case for all sorts of reasons that should be obvious.

As it turns out, hiding the Entity Framework behind a traditional Data Access Layer and using typical DAO semantics requires some clever Extension methods, a few extra lines of code on your part, resigning yourself to updating more fields than actually changed, and some consideration of how this might cause the whole Entity Framework abstraction to leak out into other parts of your application.  It's still worth it, but I wish I'd seen some more real Entity Framework demonstrations while the excitement was building.

It's my opinion that the level of Architecture you need in demo applications is determined by knowing your audience and knowing how naturally the quick and dirty vanilla demo code can be translated into the design of a production ready application.  Those who attend and present at User Group talks should be ready to ask and answer these questions.



Tuesday, October 21, 2008 11:59:37 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, October 19, 2008

The AGT (Argentum Tela) series of articles is an effort to do two things.  Usually an idea is presented only in its finished form.  The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches.  The second goal, and the example vehicle for the evolution aspect, is an extensible Design Surface for Silverlight similar to what we have in Visual Studio 2008.   This type of application has all sorts of interesting uses.  My example is a Home Theater layout tool.  Read the entire saga: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx

Moving the Selection with the mouse

Now that select and multi-select are working the next logical step is to allow the user to move things around on the design surface, and I have an idea of what I’d like to support. 

·         A single selected component is straightforward: drag and move.

·         If there are multiple components selected, clicking on any one of them should move the entire selection without changing relative space.

The first item is probably best accomplished by handling mouse events and mouse capture on the invisible “Glass” component of the DesignSite.

Refactor: Some of the mouse event handlers in DesignSite were still named Visual_MouseXXX and should be called Glass_MouseXXX now.

Refactor: Right about this time, Silverlight Release To Web came out.  So far, I’ve not had to change anything from my RC0 code.

We need to decide where to wire up the mouse events associated with moving – in the DesignSurface or in the DesignSite.   Since we are already using the ISelectionService within DesignSite, I’ll try there first.  Remember that the “Glass” control in the DesignSite is the 99% transparent overlay on top of the hosted content.  We need to capture the mouse from the Glass component:

        void Glass_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {

            _selSvc.Select(new List<IDesignableControl> { this });

            _isMoving = true;

            _localMovePoint = e.GetPosition(Glass);

            _surfaceMousePoint = e.GetPosition(DesignParent);

            e.Handled = true;

            Glass.CaptureMouse();

        }

First I test to make sure I can move a single selected component around:

        private void Glass_MouseMove(object sender, MouseEventArgs e)

        {

            if (_isMoving)

            {

                Point surfacePoint = e.GetPosition(DesignParent);

                //Since we can't grab from outside ourself:

                double left = surfacePoint.X - _localMovePoint.X;

                double top = surfacePoint.Y - _localMovePoint.Y;

               

                SetValue(Canvas.LeftProperty, left);

                SetValue(Canvas.TopProperty, top);

            }

        }

This works exactly as I’d expect it to.  In order to move multiple selected components at once, I’m expecting that I’ll have to calculate a relative change from the previous mouse location and iterate across all IDCs in the selection, updating their positions.

My selection-code-under-Glass was clearing the multi-selection and replacing it with a single selection, so when I update the Glass_MouseLeftButtonDown method, everything works.

        void Glass_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {

            //if this is already selected, we might want to move.

            if ( (_selSvc.SelectionCount > 0 && _selSvc.GetSelection().Contains(this)))

            {

                StartMove(e);

            }

            else if (!_selSvc.GetSelection().Contains(this))

            {

                _selSvc.Select(new List<IDesignableControl> { this });

                StartMove(e);

            }

            e.Handled = true;

        }

 

        private void StartMove(MouseButtonEventArgs e)

        {

            _isMoving = true;

            _localMovePoint = e.GetPosition(Glass);

            _surfaceMousePoint = e.GetPosition(DesignParent);

            Glass.CaptureMouse();

        }

Refactor: I found the app behaved more intuitively when I set it so that clicking on the surface itself would clear the current selection.  This is done by passing null to ISelectionService.Select().

I can now select and move things around on the surface.

In the next article I’m going to finally start working on replacing that section on the right that has said “I am a property grid” for 15 articles so far.  The live demo has been updated, which means you’ll need Silverlight RTW to run it.

Source code: DamonPayne.AGT[14].zip (805.28 KB)



Sunday, October 19, 2008 2:50:48 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Back in the first Code Refactoring article in the AGT series I mentioned that I'd like to keep in mind flexibility for using aspects of the framework that were not specific to the designer issue on other projects.  I sat down to do some work on another Silverlight project tonight, and found that I needed to do make some changes to the DamonPayne.AG.IoC framework.  I didn't have to change much, and indeed many people would have foreseen these things before now.  I am a big believer in Just in Time Design - plan for just as much as you know you need, or at least strongly suspect you'll need, and let other patterns emerge through use.  This only works if you're doing enough Use to actually facilitate some meaningful Learning.  Branching out to the second client project, one that does something utterly different than AGT, is a good step forward.

Refactor: I immediately found that I needed a way to Remove an IView that had served its purpose, and that this method was missing from the IRegionManager interface, so I added this.

Refactor: I found that there were some cases where I wanted an AggregateEvent to signify that something had happened, but there was no event payload type necessary.  I changed EventAggregator to allow this and created AggregateEvent, differentiated from AggregateEvent<T>.  While doing this, I found another instance where I was wasting time with ConstructorInfo instead of just using Activator.

In the end, that's not much refactoring for a first attempt at using a framework for another project.  I'm getting ready to possibly use this for more Silverlight projects at CarSpot which should further increase the quality and flexibility of the codebase.



Sunday, October 19, 2008 9:19:42 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, October 15, 2008

We interrupt normally scheduled article writing to bring you a public service announcement concerning the PS3/Xbox360/PC game called Dead Space, widely available today.  I pre ordered this game thinking I'd take a chance; survival horror Movies are my thing, games not so much.  The early impressions started rolling in and they were so overwhelmingly positive I started to get excited.  Two-channel music and gaming in my home theater are excellent sources of stress relief, but Cod4 has started to get old.  I've been eagerly awating Dead Space and the Ars Technica Review did not encourage patience.  These days I do my gaming in a very large dedicated room on a 106" screen with good surround sound.  Let me just say that barely 2 hours into this game, it is executed so well it's unbelievable.  The use of immersive sound scares you, you'll ooh and ahhh at the attention to detail, and then you will witness something horrific that requires a a forceful and horrific response on your part.  This game has flow down to a science, and has other reviewers have noted, they took an innovated approach to the necesary evils of inventory and weapon switching and such: it's all integrated into the game.  You never leave the action.  Enemies will ambush you while you are reading the various logs that you find.  This game is awesome.  If you like horror, do not have a weak stomach, and can play games in the dark, you must buy it.

 



Wednesday, October 15, 2008 10:12:24 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, October 14, 2008

Silverlight 2 is a real, shipping product today.

http://www.hanselman.com/blog/Silverlight2IsOut.aspx

Later this week, I'll be converting my development environment and projects (including AGT) over to the Real Deal, and hopefully releasing some stuff at CarSpot too.



Tuesday, October 14, 2008 8:13:23 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, October 13, 2008

I will not be there due to family being in town, but I encourage you to check out David Palfery's presentation in the Milwaukee area tomorrow night: http://www.wi-ineta.org/.  If you'd like a preview of what he's presenting, check out his blog at http://palfery.spaces.live.com/ .

There's also a Silverlight SIG branch of our User Group now, and I sadly missed the first meeting.  Perhaps in the future Milwaukee Silverlight coders would like to see a presentation on a Silverlight IoC framework and its application to a Visio-type drawing tool?



Monday, October 13, 2008 1:22:26 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Hey, I got mentioned on the world famous Thirsty Developer podcast.



Monday, October 13, 2008 8:44:32 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

No, I'm not going to the PDC, much to my dismay, so I have to live on the crumbs we'll get from blogs. 

PFX will be a part of the .NET framework 4.0.  This is good news!  I have to wonder, though, if this will make it to Silverlight and the Compact Framework?  I am working on some Silverlight performance benchmark stuff and was contemplating porting some of my own work just to see how much effort it would be.



Monday, October 13, 2008 8:32:04 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, October 09, 2008

The AGT (Argentum Tela) series of articles is an effort to do two things.  Usually an idea is presented only in its finished form.  The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches.  The second goal, and the example vehicle for the evolution aspect, is an extensible Design Surface for Silverlight similar to what we have in Visual Studio 2008.   This type of application has all sorts of interesting uses.  My example is a Home Theater layout tool.  Read the entire saga: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx

Rectangular Lasso

I find it incredibly useful to be able to draw a temporary rectangle on a design surface in order to select multiple Controls at once for moving or property editing.  I’d like to support this feature in AGT as well; I call it the rectangular lasso.  I had a very good notion of how I was going to do this: use a method somewhat similar to how I calculated render size in my resizing code, but for drawing a Rectangle object on my design surface.  I’m going to introduce a new convention for these articles: a quick heading to summarize any time that I find I needed to refactor my design to meet current needs or refine thinking.  Since refactoring is good, these will be green and easy to see.

Refactor: I made a static class called ControlExtensions and included a Generic Method to be more convenient with returning Dependency Property values.  When I tried to use this with the Rectangle code below I realized (duh) that this should not be for Control, but DependencyObject.  A DependencyObjectExtensions class has been introduced.

The stage is set when I click on the design surface Canvas. 

        private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {

            _isSelecting = true;

            _selectingRect = new Rectangle();

            _selectingRect.StrokeThickness = SELECT_RECT_STROKE_WIDTH;

            _selectingRect.RadiusX = SELECT_RECT_RADIUS_X;

            _selectingRect.RadiusY = SELECT_REDT_RADIUS_Y;

            _selectingRect.Stroke = new SolidColorBrush(Colors.Red);

            _selectingRect.Fill = new SolidColorBrush(Colors.Magenta);

            Point clickLoc = e.GetPosition(LayoutRoot);

            _selectingRect.Opacity = .65;

            _selectingRect.SetValue(Canvas.ZIndexProperty, 100);

            _selectingRect.SetValue(Canvas.LeftProperty, clickLoc.X);

            _selectingRect.SetValue(Canvas.TopProperty, clickLoc.Y);

            _selectAnchor = clickLoc;

            LayoutRoot.Children.Add(_selectingRect);

            LayoutRoot.CaptureMouse();

        }

This sets up the Rectangle for resizing.  It will be on top of all other controls and transparent so we can see the controls beneath it.

Note: since some Silverlight components cannot be extended, one is likely to be working with a lot of UserControls.   I like the convention of keeping the top level content of each UserControl named LayoutRoot regardless of its type, it’s a good name.

We next need to handle the mouse move event as long as the left button is pressed.  I have noticed that various programs with a design surface seem to allow resizing only to the east and south, but selecting in either direction.  Since this seems to be a convention, I’ll follow it. 

        private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)

        {

            _localMousePos = e.GetPosition(this);

            if (_isSelecting)//southeast drag

            {

                double rectLeft = _selectingRect.GetValue<double>(Canvas.LeftProperty);

                double rectTop = _selectingRect.GetValue<double>(Canvas.TopProperty);

                if (rectLeft < _localMousePos.X)

                {

                    double width = _localMousePos.X - rectLeft;

                    double height = _localMousePos.Y - rectTop;

                    _selectingRect.Width = width;

                    _selectingRect.Height = height;

                }

                else // northwest drag

                {

                    double width = _selectAnchor.X - _localMousePos.X;

                    double height = _selectAnchor.Y - _localMousePos.Y;

                    if (width > 0 && height > 0)//need this safety here in case a resize rectangle "crosses" itself.

                    {

                        _selectingRect.Width = width;

                        _selectingRect.Height = height;

                        _selectingRect.SetValue(Canvas.LeftProperty, _localMousePos.X);

                        _selectingRect.SetValue(Canvas.TopProperty, _localMousePos.Y);

                    }

                }

            }

        }

Refactor: When testing the drawing of the selection Rectangle, I found that when resizing controls on the surface I would also have a tag-along selection rectangle.  The DesignSite resizing Border needed to mark the mouse click event has Handled=true in order to prevent this.

Now when the user releases the left mouse button, we destroy the selection Rectangle:

        private void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

        {

            _isSelecting = false;

            LayoutRoot.ReleaseMouseCapture();

            LayoutRoot.Children.Remove(_selectingRect);

            _selectingRect = null;

        }

This now allows me to lasso in several controls at once like so:

The last aspect of this feature is to hook into the ISelectionService to select any components that are within the Rectangle bounds as I resize the Rectangle.  We need to create a SelectLassoComponents method and call it from within the MouseMove event.  As soon as this is implemented, I find some new strange behavior.  FindElementsInHostCoordinates does not work like I’d hoped.  It seems to need a much bigger Rectangle than it appears to visually to call a “hit” for a control:

       

So, I add logging to see the size of the rectangle I’m drawing in my message window.  I see nothing but extremely odd behavior.  The code for selecting the lassoed controls uses the VisaulTreeHelper class as follows:

        protected virtual void SelectLassoComponents(Rectangle lasso)

        {           

            double left = lasso.GetValue<double>(Canvas.LeftProperty);

            double top = lasso.GetValue<double>(Canvas.TopProperty);

            double width = lasso.Width;

            double height = lasso.Height;

            Rect r = new Rect(left,top,width,height);           

            IEnumerable<UIElement> hits =

                VisualTreeHelper.FindElementsInHostCoordinates(r, LayoutRoot);

VisualTreeHelper is not yet documented on MSDN, but my logging tells me that the Rect is being drawn with the correct bounds.  I had to write my own simple hit test function which will register a hit if any of the four corners of a control falls within the lasso.  This required a FrameworkElementExtensions class to contain a corner method:

        public static Point[] GetCanvasCorners(this FrameworkElement u)

        {

            if (!(u.Parent is Canvas))

            {

                throw new ArgumentException("This only works if FrameworkElement is on a Canas!");

            }

 

            Point[] corners = new Point[4];

 

            double left = u.GetValue<double>(Canvas.LeftProperty);

            double top = u.GetValue<double>(Canvas.TopProperty);

            double width = u.Width;

            double height = u.Height;

            if (double.IsNaN(width) || double.IsNaN(height))

            {

                width = u.RenderSize.Width;

                height = u.RenderSize.Height;

            }

 

            corners[0] = new Point(left, top);

            corners[1] = new Point(left + width, top);

            corners[2] = new Point(left, top + height);

            corners[3] = new Point(left + width, top + height);

 

            return corners;

        }

Following this, we can use a more basic hit testing algorithm like what is shown here, within the SelectLassoComponents implementation.

        protected virtual void SelectLassoComponents(Rectangle lasso)//TODO: make hit test strategy pluggable

        {           

            double left = lasso.GetValue<double>(Canvas.LeftProperty);

            double top = lasso.GetValue<double>(Canvas.TopProperty);

            double width = lasso.Width;

            double height = lasso.Height;

            Rect r = new Rect(left,top,width,height);           

            List<IDesignableControl> selection = new List<IDesignableControl>();

 

            foreach (var u in LayoutRoot.Children)

            {

                if (u is IDesignableControl)

                {

                    IDesignableControl test = (IDesignableControl)u;

                    Point[] corners = test.Visual.GetCanvasCorners();

                    for (int i = 0; i < corners.Length; ++i)

                    {

                        if (r.Contains(corners[i]))

                        {

                            selection.Add(test);

                            break;

                        }

                        else

                        {

                            Log.Log(new DamonPayne.AG.IoC.Types.LogMessage

                            {

                                Level = DamonPayne.AG.IoC.Types.LogLevels.Debug,

                                Message = r.ToString() + " does not contain " + corners[i]

                            });

                        }

                    }

                }

            }

 

            SelectionSvc.Select(selection);

        }

This feels dirty, like I’m still thinking like a Winforms developer, but I’m testing so far with controls that are not Transformed or anything hokey, so I’m disappointed that the VisualTreeHelper method misbehaved on even the simplest case.  This code works exactly as I’d hope it to.  I’m ready to set the lasso aside and move on.

The live demo has been updated!

Source code: DamonPayne.AGT[13].zip (1.47 MB)



Thursday, October 09, 2008 7:49:30 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I should have done this a long time ago since Google analytics does not track RSS subscribers.

Please update your syndication links to use feedburner: http://feeds.feedburner.com/damonpayne



Thursday, October 09, 2008 9:50:16 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, October 08, 2008

I have the dubious honor of being the subject of Matt Terski's last blog post from quite some time ago, but The Terski is blogging again, this time at The Use Case Blog - sponsored by his company Serlio Software.  While the articles mostly show their worth all by themselves, keep in mind that Terski and his partners are no strangers to the Tool space, having previously been at a little company you may have heard of - Rational.

I definately agree with the latest post: http://blog.casecomplete.com/post.aspx?id=2c94856f-2e99-464a-86ba-c9184c393fd3

There is a consulting company here in Milwaukee, one that I generally like, who is going after the "Application Lifecycle" segment fairly hard.  Their goal is to use Team System to help clients tie every line of code back to a business goal.  While this may seem admirable on the surface, I Recoil in Horror from this type of thinking.  What benefit does a business think it will get from doing this?  Is squashing creativity and necessary risk taking so important?  There is a principle in Physics that states the act of measuring something alters the results obtained from the measurement.  Most business people today in the upper echelons of a publicly traded company would be happy to tell you the detrimental effects that Sarbanes-Oxley has on their day to day operations, so why impose something even more draconian on software development?

The bottom line is that many, many features of popular software development or requirements tools are much better at selling licenses than they are at solving any real problem.



Wednesday, October 08, 2008 12:29:33 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I have had a couple of requests to post the entire source code for my Silverlight 2 Image carousel.  I plan on updating the code for RC0, cleaning it up, and making it not specific to Images, and posting the code here.  Stay tuned.



Wednesday, October 08, 2008 10:09:50 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, October 07, 2008
My friends, family, co-workers, and Twitter followers know that I am a huge fan of red wine.  When I recently tabulated and estimated what I've spent on wine to drink, wine to store, and wine to gift in the past 12 months I was very nearly ashamed.  Nearly, but not quite!  As it turns out, though, it's all for the good.  Here's yet another study linking the antioxidants in red wine to some sort of cancer prevention:

http://tinyurl.com/44aoc3

Now if only the price of Bourgogne and Chateauneuf du Pape would stop going up.



Tuesday, October 07, 2008 9:38:02 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

This is a roundup for the AGT project: what have we done so far?

If you’re just joining me, I always put the following text at the beginning of each article in the series I’m currently working on:

The AGT (Argentum Tela) series of articles is an effort to do two things.  Usually an idea is presented only in its finished form.  The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches.  The second goal, and the example vehicle for the evolution aspect, is an extensible Design Surface for Silverlight similar to what we have in Visual Studio 2008.   This type of application has all sorts of interesting uses.  My example is a Home Theater layout tool.

What I'm working on here is a framwork I can use to build something that looks and behaves like a Visual Studio Shell or a Visio, but runs inside the browser using Silverlight.  I of course won’t claim to have duplicated the functionality in those tools, which is massive.  Rather, I’m going for the “80/20” rule; I’m hoping that I can achieve huge amounts of functionality for very little work with an implementation that is understandable yet flexible.  I plan on putting this on Codeplex once I get to a decent v1 milestone. 

Summary of the ground covered so far:

·         AGT[0] – Vision statement

·         AGT[1] – Initial thoughts on Service/plug-in/add-in/module strategy and Composite UI management

·         AGT[2] – Dependency injection, Presenters, Event Aggregation

·         AGT[3] – Creating the initial designer metaphors

·         AGT[4] – Toolbox service, ViewModel, Data binding with INotifyCollectionChanged

·         AGT[5] – Generating dynamic proxies for Service providers using Reflection Emit

·         AGT[6] – Creating a design for drag/drop interactions in Silverlight.

·         AGT[7] – Implementing drag ‘n drop from the Toolbox to the Design surface

·         AGT[8] – Code refactoring and introduction of Unit Tests for Silverlight

·         AGT[9] – Visual refactoring, new Toolbox items

·         AGT[10] – Creating a Selection service, publish live demo

·         AGT[11] – Updating the codebase for Silverlight 2 RC0

·         AGT[12] – Resizing items on the designer surface

This is Reality Blogging, I really am writing the code as I go.  Some of the embarrassing refactoring should serve as proof of this.  Still, it’s completely fair to think about a roadmap and potential problems.  I do have an idea of where all this is going and what might constitute a reasonable v1 release.  Here are some additional things I'd like to support.

1.       Multi-select items by drawing a rectangle “lasso” on the surface

2.       Moving  the selected stuff around on the surface with the mouse and keyboard

3.       Property Editing contract: When an IDesignableControl is selected, how will I determine what properties show up in my “property grid” and how they look?

4.       Property editing grid: with the above decided, a PropertyGrid will have to be built from scratch!

5.       Supporting Silverlight 2’s Full Screen mode

6.       Support zoom in/zoom out at the document level

7.       Change the look of the designer using some kind of Settings service.

8.       Design Surface Behavior concepts:  I have this idea that I might provide flexibility as to how things look and behave on the Designer Surface using a “behavior chain”, which I envision will function as a set of mini-Services hosted by the Design Surface itself. I thought of some things I might do to prove the design works:

a.       Alignment snap-lines ala Visual Studio?

b.      Rotate “adorner” to allow things to be rotated?

c.       Measure adorner to selected items show a “size”?

d.      Refactor multi-select to be a behavior of this type?

9.       Serialization/IDesignableControl<T>: In order to be useful, my “drawings” will need to be saved and re-loaded into the designer!  I need to serialize the scene graph into some kind of "document".

10.   Hierarchy of service containers via some notion of Context.  Stay tuned for explanation on this one…

11.   “Stencil” sets: how to provide drop-in flexibility for “what kind of document is this?”

12.   Dedicated refactoring articles as I go.

If I am correct and these things can mostly be done in one article each, I might be up to something like AGT[30] before I’m ready to put this on Codeplex as an v1 release. I'm excited about what's to come, but I only have so much time for research so I will take each step as I can.



Tuesday, October 07, 2008 12:33:30 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, October 06, 2008

The AGT (Argentum Tela) series of articles is an effort to do two things.  Usually an idea is presented only in its finished form.  The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches.  The second goal, and the example vehicle for the evolution aspect, is an extensible Design Surface for Silverlight similar to what we have in Visual Studio 2008.   This type of application has all sorts of interesting uses.  My example is a Home Theater layout tool.  Read the entire saga: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx

Resizing

As I look at the project so far, it seems somehow more natural to want to move things before selecting or resizing, but the notion of Selection had to come first, which has a dependency on my Site metaphor.  Since the selection border is already in place, why not move on to resizing next?

While contemplating resizing, rotation (which I plan on adding eventually), and selection I had a dilemma.  I felt there were two main paths:

1.       Draw adorners.  This means the IDesignableControl lives on the surface mostly as itself, and when an interesting event happens (like selection) we have the surface draw a border with the familiar “grab knobs” at the corners. 

2.       Container/Site: I first thought of creating a Grid to hold the IDesignableControl within it, since this would provide the table structure needed to draw “knobs” as well as the familiar selection border.

I wrote the code for the Grid first while working in my third office (http://www.ale-house.com/alehouse/index.php?option=com_content&task=view&id=23&Itemid=57 ) without the source for the project, which lives on the workstation in my second office (http://www.flickr.com/photos/damonrpayne/528817886/ ) and ultimately didn’t’ like it, but I did learn enough to see that a Silverlight Border would make resizing incredibly easy.  I have a vague notion of an IDesignSurfaceBehavior I can use for true Adorner flexibility later, but for selection and resizing using a UserControl with a Border was just so simple I had to start by going down this path.

DesignSite

The UserControl serving as border container is the DesignSite.  It has a Width and Height of Auto and the Border has a Width and Height of Auto as well, to make resizing a piece of cake.  The XAML couldn’t be easier.

        <Border x:Name="SiteBorder" BorderThickness="0" BorderBrush="{StaticResource SelectionBorderBrush}" CornerRadius="7" Cursor="Hand" MouseLeftButtonDown="SiteBorder_MouseLeftButtonDown" MouseLeftButtonUp="SiteBorder_MouseLeftButtonUp" MouseMove="SiteBorder_MouseMove">

            <Border.Child>

                <TextBlock>Default Content</TextBlock>

            </Border.Child>

        </Border>

The code is fairly straightforward too.  For most things we are using a pattern of delegation to the contained instance to make future designer interactions easier.  In order to properly calculate resizing, I needed to be able to get at the actual design surface itself, so I created an IDesigner interface which is implemented by DesignSurface.

    public interface IDesigner : IService

    {

  UserControl VisualRoot { get; }

  Canvas Surface { get; }

    }

I have a feeling I’ll be adding to that later.  Here is the mouse code in DesignSite:

        private void SiteBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {           

            _resizing = true;

            SiteBorder.CaptureMouse();

        }

 

        private void SiteBorder_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

        {

            _resizing = false;

            SiteBorder.ReleaseMouseCapture();

        }

 

        private void SiteBorder_MouseMove(object sender, MouseEventArgs e)

        {

            if (_resizing)

            {

                Point resizePoint = e.GetPosition(_designer.VisualRoot);

                double left = SiteBorder.GetValue<double>(Canvas.LeftProperty);

                double top = SiteBorder.GetValue<double>(Canvas.TopProperty);

                double newWidth = left + resizePoint.X;

                double newHeight = top + resizePoint.Y;

                Control c = (Control)SiteBorder.Child;

                if (newWidth > 5 && newHeight > 5)

                {

                    c.Width = newWidth;

                    c.Height = newHeight;

                }               

            }

        }

And now, when I run it, everything goes to hell.  Handling resizing seems to have become my first serious problem.  In order to study the issue, I created a DummyButton IDesignableControl, which exposes even more problems. 

A Silverlight control with size set to Auto (take up its whole container) returns NaN for Width and Height,  {0,0} for RenderSize, 0.0 for ActualWidth and ActualHeight, {0,0} for DesiredSize, 0.0 for MinWidth and MinHeight.  This makes it hard to measure or do any calculations on until it is actually placed in a container.  Calling Control.Measure does not seem to fix the issue.  I was unable to create my Drag representation of the DummyButton until I set its containing UserControl to have an actual size.  Attempting to select the button on the surface reveals another issue: the button is swallowing the click event before my code can react to it.  I’d like to handle this in a generic fashion.

I set about making a “glass” idea: placing an almost entirely transparent control in front of the real content in the Z order.  Getting this to cover the entirety of the real content led me back to the same lack of ActualWidth and friends that started this mess. 

Reality Blogging, cue RC0

OK, everything above this heading was written pre-RC0 and I was clearly floundering, I call time out.  I need to restate and reform my goals:

·         My DummyButton test shows that I need to refine IDesignableControl and how things ought to resize on the DesignSurface.  I am so far setting width/height of the IDesignableControl.Visual when dragging the Border to resize.  This is only appropriate in some cases. For a Button, sure, I’d want to set its bounds independent of its FontSize or other properties.  For the furniture and speakers, I’d actually want to apply a Transform since a huge red square with a chair in the corner or a small red square showing part of a chair is obviously not my intent.

·         Following from the above, IDesignableControl.Scalable and IDesignableControl.Transformable need to be re-thought.  I need a property stating if the IDC{IDesignableControl} is to be resized by changing Width & Height, if not, Transformable should be checked.  Both these properties being false means we should not resize the IDC at all.

·         There’s too much code in DesignSite.  Event wire up related to sizing is possibly fine, but we should keep things in whatever IDesignTypeCreator implementation wherever possible.

Now I feel like I’ve got a better plan, even if the Measuring issue is still waiting.  First I refactor IDC.Scalable and include an appropriate comment.

        /// <summary>

        /// Return true if this IDesignableControl should be resized by changing Width & Height

        /// </summary>

        bool IsBoundsResizable { get; }

Next, IDC.Transformable:

        /// <summary>

        /// Can a ScaleTransform etc. be used to resize this control?

        /// </summary>

        bool IsTransformable { get; }

Next I setup a test. The DummyButton will be IsBoundsResizable=true, and the Red Chair will be IsBoundsResizable=fasle/IsTransformable=true.

Debugging Events

The next step I take is to set up some code to debug the various mouse position, sizing, and layout events for the Border in DesignSite.  I tend to do this by instrumenting the code rather than using the debugger.  What good is a MouseLeftButtonDown event on a visual control when the debugger will receive the MouseLeftButtonUp event?  I have to use the ILogService and my message window to see when/how some things are happening.

Layout Cycle Detected

If you subscribe to a LayoutUpdated event, it’s best not to touch the GUI in any way from within this event handler.  During my testing, I would use the ILogService to send a message stating that my DesignSite had its layout updated.  This, in turn, fires the LayoutUpdated event which is counterintuitive.  One might think that LayoutUpdated means “Layout of the current control” but in fact means a layout or property change of any item in the visual tree of the Silverlight plug-in.  Obviously touching the visual tree from here will melt circuits.

Resizing

With a more clear idea of precisely what I was trying to do, I went back to working on my resizing code. When my Border has mouse capture, I can choose how to resize its content:

        private void SiteBorder_MouseMove(object sender, MouseEventArgs e)

        {

            if (_resizing)

            {

                if (HostedContent.IsBoundsResizable)

                {

                    ResizeContent(e);

                }

                else

                {

                    TransformContent(e);

                }               

            }

        }

I have to admit it took quite a while to get the ResizeContent method right.  At the end of the day, this was just facing reality and doing what worked rather than refusing to accept how”Auto”, “Stretch”, and “Fill” did not live up to expectations.  I have to set five different Controls’ sizes manually to make my bounds resizing work:

        private void ResizeContent(MouseEventArgs e)

        {

            Point resizePoint = e.GetPosition(DesignParent);

            //Determine where the DesignSite is on parent

            double left = this.GetValue<double>(Canvas.LeftProperty);

            double top = this.GetValue<double>(Canvas.TopProperty);

            double newWidth = resizePoint.X - left;

            double newHeight = resizePoint.Y - top;

 

            //Take border into account

            double innerWidth = newWidth - (2 * UNIFORM_BORDER_THICKNESS);

            double innerHeight = newHeight - (2 * UNIFORM_BORDER_THICKNESS);

 

            if (newWidth > 5 && newHeight > 5)

            {

                _content.Visual.Width = innerWidth;

                _content.Visual.Height = innerHeight;

                Glass.Width = innerWidth;

                Glass.Height = innerHeight;

                LayoutRoot.Width = newWidth;

                LayoutRoot.Height = newHeight;

               

                ContentCanvas.Width = innerWidth;

                ContentCanvas.Height = innerHeight;

 

                SiteBorder.Width = newWidth;

                SiteBorder.Height = newHeight;

 

            }

        }

Having accepted this reality, my “transform” resizing worked the first time:

        private void TransformContent(MouseEventArgs e)

        {

            Point resizePoint = e.GetPosition(DesignParent);

            //Determine where the DesignSite is on parent

            double left = this.GetValue<double>(Canvas.LeftProperty);

            double top = this.GetValue<double>(Canvas.TopProperty);

            double newWidth = resizePoint.X - left;

            double newHeight = resizePoint.Y - top;

            //We need to attain a render size equal to our new border inner bounds,

            //so we'll calculate what scale transform would get us there

            double innerWidth = newWidth - (2 * UNIFORM_BORDER_THICKNESS);

            double innerHeight = newHeight - (2 * UNIFORM_BORDER_THICKNESS);

 

            SiteBorder.Width = newWidth;

            SiteBorder.Height = newHeight;

            //

            Glass.Width = innerWidth;

            Glass.Height = innerHeight;

            //

            ContentCanvas.Width = innerWidth;

            ContentCanvas.Height = innerHeight;

            //

            ScaleTransform st = new ScaleTransform();

            st.ScaleX = innerWidth / HostedContent.Visual.ActualWidth;

            st.ScaleY = innerHeight / HostedContent.Visual.ActualHeight;

            HostedContent.Visual.RenderTransform = st;

        }

Now, I can finally have large buttons, small couches, and huge speakers on my design surface:

… All perfectly scaled according to their needs by naturally grabbing and dragging the border.  Hooray for vector graphics.  Note to self: every single flaw in artwork is readily apparent when scaled far larger than I drew it.

Conclusion

This article was a bit of a mess since Silverlight 2 RC0 happened right in the middle of it and my vision was not re-adjusted early enough when I started realizing there were issues.  There are also still cases where I am obviously thinking like a WinForms developer.  I kept thinking to myself how easy this would be if I could do work by overriding an OnPaint virtual method.  Still, I finally got where I wanted to be and learned a few more things about Silverlight in the process.  Since I’ve already given thought to one more bit of sauce before I write code to move things around on the surface I will visit the “rectangular lasso” next.

Source code: DamonPayne.AGT[12].zip (1.3 MB)



Monday, October 06, 2008 10:32:00 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, October 01, 2008

I don’t have any ideas yet, but Netflix is one of my favorite services.   This is completely awesome.  I hope they have the APIs enabled for cross-domain stuff so I could perhaps do some Silverlight mojo without having to Proxy the calls.

http://developer.netflix.com/page

Actually, in the process of writing this email, I did get some ideas for uses of the Netflix API.  I'm not sharing yet!



Wednesday, October 01, 2008 1:52:56 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback