In part one, a buttonlike control was created. It worked like a button, or at least a little. It was a square with a descriptive text and a brand new Click event. Unfortunately that doesn't really make it feel like a button. There is more to a button, especially in Silverlight and WPF. First off, a button normally gives some visual feedback to the user, confirming that it is pressed or that the button is hovering over it. That visual feedback is something that you find in most button implementation, wether it is in Windows Forms, HTML or WPF. The thing that is special with buttons in WPF and Silverlight is that the content of the button - in this case the descriptive text - can be anything. And I mean ANYTHING. It could be a Grid with several controls inside. Not that I would recommend doing some of the things that you CAN do, but it is possible. So in this part of the tutorial, the button will get visual feedback and support for complex content.

Lets start off with the support to add anything as content in the control. To add this support is actually pretty simple. First off the TextBlock, that is in the template now, must be removed. Or actually not so much removed as replaced, replaced with a ContentPresenter. The the inheritance of the control has to be changed from Control to ContentControl. That's all there is to it. Oh..yeah...and remember to remove the Text property as well, since it isn't being used anymore it shouldn't be in there...

<TextBlock Text="{TemplateBinding Text}" 
           HorizontalAlignment="Center" 
           VerticalAlignment="Center"
           Foreground="{TemplateBinding Foreground}"/>

Is replaced with

<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />

And the inheritance of the control is changed to

public class MyButton : ContentControl
{
    ...
}

So...this change now forces a change in the "test button". Change the "test button" from using the Text property to use the newly added Content property instead. It could look something like the following

<btn:MyButton Width="100" Height="50" Background="Yellow" Foreground="White" Click="MyButton_Click">
    <btn:MyButton.Content>
        <TextBlock Text="Hello Content" />
    </btn:MyButton.Content>
</btn:MyButton>

The eventhandler of course has to be changed as well, since it works with the Text property. To have the same effect, the new TextBlock has to be named and then modified from the handler

<btn:MyButton Width="100" Height="50" Background="Yellow" Foreground="White" Click="MyButton_Click">
    <btn:MyButton.Content>
        <TextBlock Text="Hello Content" x:Name="ContentText" />
    </btn:MyButton.Content>
</btn:MyButton>
private void MyButton_Click(object sender, MouseEventArgs e)
{
    ContentText.Text = "Hello again";
}

So...now that the content part has been changed, it is time to take care of the visual changes. In the Silverlight 2 beta, all visual changes where based on animations with specific names that were then called from the controls code. The idea is the same today, but it is a little less "fragile". Animations are still responsible for the visual changes, but they aren't called by name from code anymore. This use of specific named is too "fragile". Instead a new control was introduced, the VisualStateManager. The VisualStateManager is responsible for setting states. A state is a specific visual state. So the different states are defined in the VSM and then the code uses the VSM to set. How does this make it less fragile? Well, this will be shown in a little while.

The cool thing about the VSM is that it supports states and state groups. Only one state within a state group can be set at once. But multiple state groups can have states set. So in the MyButton control, there will be only one state group. But if you have a look at for example the Button control it uses two groups, "FocusStates" and "CommonStates". But as said before, the MyButton control will only have a single state group called "CommonStates". Inside that group, it will have 3 states - "Normal", "MouseOver" and "Pressed". These states might not seem logical to everyone, but the cool thing is that every developer can choose the names of the groups and states by themselves. So in this case...it is like this...comprende...

So...to start using the VSM it has to be added to our xaml. This isn't really hard, but it is in a separate assembly and namespace so we need a new xml namespace in the xaml.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyButton"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    >
...

Next it is time to add the state groups and states. This can be done from 2 directions. Either setting them in code first and them create the xaml, or starting from xaml and then doing the code. It doesn't really matter as long as the different groups and states are already set on paper somewhere... In this case, it will start in the xaml. The VSM is added inside the root Grid like this

<Grid Background="{TemplateBinding Background}">
    <vsm:VisualStateManager.VisualStateGroups>
        <vsm:VisualStateGroup x:Name="CommonStates">
            <vsm:VisualState x:Name="Normal" />
            <vsm:VisualState x:Name="MouseOver" />
            <vsm:VisualState x:Name="Pressed" />
            </vsm:VisualState>
        </vsm:VisualStateGroup>
    </vsm:VisualStateManager.VisualStateGroups>
    ...
</Grid>

Now that all the group and all the states are in there, it is time to add the actual visual changes. This is done by adding animations inside the VisualState elements. So select the states that should be handled and add a Storyboard inside that VisualState. In this case the Normal state will be left empty, that will make it return to the original layout when that state is set. So to handle the MouseOver and Pressed states, the Xaml is changed like this

<vsm:VisualStateManager.VisualStateGroups>
    <vsm:VisualStateGroup x:Name="CommonStates">
        <vsm:VisualState x:Name="Normal" />
        <vsm:VisualState x:Name="MouseOver">
            <Storyboard>
                <ColorAnimation Storyboard.TargetName="BackgroundBrush" 
                    Storyboard.TargetProperty="Color" To="Red" />
                <DoubleAnimation Storyboard.TargetName="BackgroundBrush" 
                    Storyboard.TargetProperty="Opacity" To=".5" />
            </Storyboard>
        </vsm:VisualState>
        <vsm:VisualState x:Name="Pressed">
            <Storyboard>
                <ColorAnimation Storyboard.TargetName="BackgroundBrush" 
                    Storyboard.TargetProperty="Color" To="DarkRed" />
                <DoubleAnimation Storyboard.TargetName="BackgroundBrush" 
                    Storyboard.TargetProperty="Opacity" To=".5" />
            </Storyboard>
        </vsm:VisualState>
    </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

That gives the button some seriously uggly effects, but it will at least make some visual changes... The only problem now, is that the time it takes for the animation is too slow. To counter this, it is possible to add a VisualTransitions telling the VSM how much time to spend on each animation. The Transitions property is set like this

<vsm:VisualStateManager.VisualStateGroups>
    <vsm:VisualStateGroup x:Name="CommonStates">
        <vsm:VisualStateGroup.Transitions>
            <vsm:VisualTransition To="Normal" GeneratedDuration="0:0:0.2"/>
            <vsm:VisualTransition To="MouseOver" GeneratedDuration="0:0:0.2"/>
            <vsm:VisualTransition To="Pressed" GeneratedDuration="0:0:0.2"/>
        </vsm:VisualStateGroup.Transitions>
        ...
</vsm:VisualStateManager.VisualStateGroups>

That is all the Xaml needed. Now it is time to start setting the states from code. The first change that is needed is to handle one more event. The event in question is the MouseEnter event. That changes the constructor to look like the following

public MyButton()
{
    DefaultStyleKey = typeof(MyButton);
    this.MouseEnter += new MouseEventHandler(MyButton_MouseEnter);
    this.MouseLeave += new MouseEventHandler(MyButton_MouseLeave);
    this.MouseLeftButtonDown += new MouseButtonEventHandler(MyButton_MouseLeftButtonDown);
    this.MouseLeftButtonUp += new MouseButtonEventHandler(MyButton_MouseLeftButtonUp);
}

The handler is all emtpy for now. It will only be used for the state handling. Now...the next part is to start handling the states. To set a specific state, all that needs to be done is to call VisualStateManager.GoToState(). It takes three parameters. First the object to set the state on, then the name of the state and finally if a transition should be used. THis is kind of cool, because it makes it possible to set the states of other objects and not only your own object...be careful though... So...lets set the state in the different handlers. No...lets not do that. What should be done is to create a method that handles the actual setting of the state. That way all state setting is handled in one place. To support this method, there needs to be some way of telling it what to set. How about sending in the state to the method. No...then you are back to the distributed handling of states... Add a couple of state bools in the class. Then the handlers can set the state bool that is relevant and then just call a state setting method and let it figure out what to do. Something like this

bool _mouseOver = false;
bool _mousePressed = false;

private void SetState()
{
    if (_mousePressed)
        VisualStateManager.GoToState(this, "Pressed", true);
    else if (_mouseOver)
        VisualStateManager.GoToState(this, "MouseOver", true);
    else
        VisualStateManager.GoToState(this, "Normal", true);
}

After adding this simple method it is possible to implement the handlers. All they have to do is set the bool values and then call SetState(). In this control this seems like "a lot" of extra work, but in a control with several state groups and and more states, it makes it a lot simpler to handle different state combinations. The handlers should look something like this

void MyButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    _mousePressed = true;
    SetState();
}
void MyButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    if (_mousePressed)
    {
        OnClick(e);
    }
    _mousePressed = false;
    SetState();
}
void MyButton_MouseEnter(object sender, MouseEventArgs e)
{
    _mouseOver = true;
    SetState();
}
void MyButton_MouseLeave(object sender, MouseEventArgs e)
{
    _mousePressed = false;
    _mouseOver = false;
    SetState();
}

That's it! Well...more or less. I was talking about how fragile the old state handling system was with specifically named animations...this doesn't seem a lot better. Well, so far it isn't. The only difference is that we get some state help from the VSM with groups and things. But it is still state names with strings that we have to figure out if we change the template. The solution to this is attributes. There are two attributes that help us when it comes to templating through tools like Expression Blend. TemplateVisualState and TemplatePart...by setting these attributes on our control, tools like Blend can automatically figure out what parts and states are available and add tooling support for it. The TemplateVisualState has 2 properties, GroupName and Name. They are both strings and define what groups and what states the controls code use. The TemplatePart attribute is a little different. It tells the tool what "parts" it needs in the template and what types they are.

Parts? What? I get states, but what is parts... Well, say that your control expects there to be a button somewhere in the template so that it can handle the user clicking it. Well, if somebody changes the template for the control, they have no idea that there should be that specific button and what it should be named unless they are told. The Slider control is a perfect example. The Slider consists of a bunch of different parts that it needs to work. The attributes for the Slider looks like this

[TemplatePart(Name="VerticalTrackLargeChangeIncreaseRepeatButton", Type=typeof(RepeatButton)), 
TemplatePart(Name="VerticalThumb",Type=typeof(Thumb)), 
TemplatePart(Name="HorizontalTemplate", Type=typeof(FrameworkElement)), 
TemplatePart(Name="HorizontalTrackLargeChangeIncreaseRepeatButton", Type=typeof(RepeatButton)), 
TemplatePart(Name="HorizontalTrackLargeChangeDecreaseRepeatButton", Type=typeof(RepeatButton)), 
TemplatePart(Name="HorizontalThumb", Type=typeof(Thumb)), 
TemplatePart(Name="VerticalTemplate", Type=typeof(FrameworkElement)), 
TemplatePart(Name="VerticalTrackLargeChangeDecreaseRepeatButton", Type=typeof(RepeatButton)),
TemplateVisualState(Name="Normal", GroupName="CommonStates"), 
TemplateVisualState(Name="Focused", GroupName="FocusStates"), 
TemplateVisualState(Name="Unfocused", GroupName="FocusStates"),  
TemplateVisualState(Name="MouseOver", GroupName="CommonStates"), 
TemplateVisualState(Name="Disabled", GroupName="CommonStates")]

Thats a lot of states and parts. So how to know what parts to add in you template if there wasn't TemplatePart attributes. The important thing when adding this attribute is to use as broad a type as possible. If you use a Rectangle in the default template, but only use features defined in UIElement, you should define the TemplatePart type to be UIElement. This makes it possible to create the widest variety of templates for the control. Again, the Slider is a perfect example. It defines the HorizontalTemplate as FrameworkElement...that is very early in the inheritance chain for Silverlight, making it possible to be VERY creative with the HorizontalTemplate.

How does a control use the parts it has defined? Well, the MyButton control wasn't complicated enough to use parts, but here is how it should have been done. Since the parts are defined in the template, it isn't possible to code against them straight off. The best way to get hold of the parts is by overriding the OnApplyTemplate() method. This is called as soon as the template is applied to the control, totally ignoring if the template if the default one or one defined by the user. Inside this method it is possible to get hold of named elements, parts, inside the template by calling GetTemplateChild(). This method will of course work outside of this method as well, as long as it is called after the template has been applied. It takes the name of the part as parameter and returns object. Use the "as" operator and beware of the very possible possibility of getting null back, since the template might not define that part. If a part is missing, there is a choice to make. Can the control work without it or should an exception be thrown. Well...it depends on the control... It could look like this

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    FrameworkElement fe = GetTemplateChild("MyPart") as FrameworkElement;
    if (fe != null)
    {
        fe.MouseEnter += ...
    }
    ...
}

Now, there are a couple of tips and convention that some people talk about. First of all, in WPF there is some convention that the name of a part should start with "PART_". In Silverlight this doesn't seem to be considered important. When dissasembling Microsofts own controls, this convention isn't followed... It's up to you...
The next thing would be to make the name of the parts into static fields... Well, that I could sort of agree with and accept. It could look something like this

[TemplatePart(Name=MyButton.MyPartName, Type=typeof(UIElement))]
public class MyButton : ContentControl
{
    public static readonly string MyPartName = "MyPartName";
    ...    
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        FrameworkElement fe = GetTemplateChild(MyButton.MyPartName) as FrameworkElement;
        ..
    }
    ...
}

But once again, that is up to the developer (you). Having it like this makes it less fragile to typing errors.

That concludes my 2 part tutorial about creating custom controls. It is far from exhausting, but should give you enough information to get started building your own controls. Supporting templates makes your controls so much more useful, since the control can be used over and over again with the same functionality, but appearing to be a totally new and good looking control. But since all "built in" Silverlight controls are templatable, make sure you aren't creating unnecessary controls. Try to re-template existing controls instead, since this is less work and less likely to introduce bugs in the system.

By the way, try adding the MyButton control to a page and use Blend to work with the template. By doing this, you will see that Blend automatically picks up the VisualState attribues and add them so that you can create the animations. Just add the control to a page, open the page in Blend and right-click the control and choose "Edit Control Parts (Template) > Create Empty". Even if the new template is empty, it will have the different states available in the States pane.

Thank you for reading and hope you got something good out of it. Don't hesistate to ask questions or add comments about the tutorial!

After having written mhy previous entry about how to style and template controls, I guess it is a good time to have a look at how to create controls that are template- and styleable. (Can you write "template- and styleable"? Looks weird...well...I am swedish so I'm allowed to write less than perfect english) Unfortunately, due to my lack of imagination, I don't have a really cool control to build and show you. So instead I'm going to create a very limited control that will work more or less as a simple button.

To start off, create a Silverlight Application project in Visual Studio 2008. When asked if a separate webproject is needed for debugging, just go with creating one on the fly. For this exercise there isn't really a need to make changes to the hosting page, whichthat means that VS can just as well create the page on the fly. After VS has created the Silverlight Application project, add another project to the solution. But this time. add a Silverlight Class Library. This isn't necessary, but having the control in a separate assembly makes re-useable. (Probably won't re-use it considering what the control does, but it keeps it real...)

For now, just ignore the application project and focus on the library. Start by removing the Class1.cs file and add a new class called MyButton. Yes...MyButton...it sucks I know, but it works... Start off by modifying the inheritance of the class. Change the class so it inherits from Control to start with. This will be changed later, but start like this.

Before moving further, there has to be a layout for the control. This should of course be Xaml. It is possible to create the layour from code, but that removes the possibility of templating the control and because of this the Xaml way is the correct way. Adding a default template to the control is not that hard, but not really obvious. Start by adding a folder called "themes" in the root of the application. Inside the themes folder, add a new Xaml file. This is easiest to do by adding an Xml file and just renaming it. It has to be named "generic.xaml". After the file has been added, some changes to it's build action has to be made. Select the file in the Solution Explorer and take a look at the Properties pane. Now there are aparently at least two settings that work here, but the oficial one is the one I will use. Set the "Build Action" to "Resource" and make sure that the "Custom Tool" part is empty. (The other way is to set the "Build Action" to "Page" and then leave/set the "Custom Tool" to "MSBuild:MarkupCompilePass1". This worked for me, but I wouldn't recommend it)

The content of the "generic.xaml" file has to start off by looking like the following:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    >
</ResourceDictionary>

So if the "generic.xaml" file was started by adding an Xml file, the Xml declaration at the top has to be removed first. Now that the "generic.xaml" has been turned into a resource dictionary, it is possible to add a default template to the MyButton control. As said in the previous post, a template is actually a specific setter inside a style, so the resource dictionary needs a style. But since the style needs to have its TargetType to point at the MyControl control we have to make a small modification to our Xaml. To be able to reference the MyButton control, an xml-namespace pointing towards the correct namespace has to be added. An xml-namespace with a clr-namespace reference is sort of like adding a "using" statemen. After the xml-namespace has been added, it is possible to add the style and point it to the right target type.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyButton"
    > 
    <Style TargetType="local:MyButton">
    </Style>
</ResourceDictionary>

Next it is time to set the template of the control by adding a setter that sets the "Template" property of the control. It is going to be a very simple template, but it will show the idea.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyButton"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    > 
    <Style TargetType="local:MyButton">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:MyButton">
                    <Grid Background="{TemplateBinding Background}">
                        <Rectangle>
                            <Rectangle.Fill>
                                <SolidColorBrush x:Name="BackgroundBrush" Opacity="0" />
                            </Rectangle.Fill>
                        </Rectangle>
                        <TextBlock Text="{TemplateBinding Text}" 
                                   HorizontalAlignment="Center" 
                                   VerticalAlignment="Center"
                                   Foreground="{TemplateBinding Foreground}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style> 
</ResourceDictionary>

The template contains 3 template bindings. A template binding will casue the value of the property with the binding to have it's value set by a property in the control. So the "{TemplateBinding Foreground}" will set the TextBlock's foreground to the foreground set in the control. A whole lot of properties are already available on the control due to its inheritance. The most interesting template binding is the one that binds the TextBlock's Text property. THis is interesting since there isn't a property called "Text" on the MyButton control. This has to be created, but first the control has to tell the system that it should use the new style/template.

In the constructor of the MyButton class, add a line of code that sets the "DefaultStyleKey" to "typeof(MyButton)". This tells the system that this control wants to use the style in the "generic.xaml" that has a TargetType that corresponds to this type. That's all that is needed.

public MyButton()
{
    DefaultStyleKey = typeof(MyButton);
}

If there is any interest in fetching parts of the template and handle those, this can be done by overriding the OnApplyTemplate() method and then getting hold of the parts by calling GetTemplateChild() passing in the name of the part. There will be more about parts and states in the next part of this tutorial...

Now, to the Text property. To be able to use a template binding, the property must be implemented as a DependencyProperty. This isn't very complicated, but necessary to make bindings work. Start off by adding a public and static DependencyProperty to the MyButton class. The naming standard says that it should be named the same thing as the property itself, but with a "Property" at the end. So in this case it should look like this:

public static DependencyProperty TextProperty;

A DependencyProperty isn't created, it is registered. In the registration the name of the property, the type of the property and the "owning" type must be supplied. The "owning" type is the type that declares the property. It is also possibe to give the property a default value as well as register a callback method that is called when the property is changed. The complete DependencyProperty declaration looks like this:

public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyButton), new PropertyMetadata(""));

Next the actual property of the MyButton type needs to be implemented. Since MyButton inherits from DependencyObject, it has one method for getting the value of a DependencyProperty as well as one to set one. They are, not surprisingly, called GetValue() and SetValue() and works with object. So the property should look something like this

public string Text
{
    get { return (string)GetValue(MyButton.TextProperty); }
    set { SetValue(MyButton.TextProperty, value); }
}

Now the layout of the button is actually done and it can be tested. There still has to be some functionality added to the control, but it will now show up if added to a page. So add a reference from the Silverlight Application project to the MyButton project. Then open up the Page.xaml file and add an xml-namespace pointing to the new assembly and also add an instance of the button. It looks like this:

<UserControl x:Class="MyButtonTest.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:btn="clr-namespace:MyButton;assembly=MyButton"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <btn:MyButton Width="100" Height="50" Text="Hello World" Background="Yellow" Foreground="Black" />
    </Grid>
</UserControl>

This should give you a view of how the control looks. The final thing to do is add some functionality. There is no use creating a control, if it doesn't have any functionality. The functionality added in this part of the 2-part tutorial, is a click event. Not very exciting, but it is some functionality. The Click event will be based on MouseLeftButonUp combined with a bool keeping track if the mousebutton was pressed down on the control too. Just handling MouseLeftButtonUp means handling a situation where the mousebutton was pressed outside of the control and then released on top of this control. So start off by adding a bool to keep track if the button has been pressed. Then add eventhandlers for MouseLeftButtonDown and Up as well as for the MouseLeave event. In the "down" handler, set the bool to true. In the "leave" set it to false. And in the "up", check to see if it is true. If it is true in the "up" event it is time to raise the Click event. Before it can be raised, it of course has to be added. Then of course set it to false before exiting the handler The code looks like this:

bool _mousePressed = false;

public MyButton()
{
    DefaultStyleKey = typeof(MyButton);
    this.MouseLeave += new MouseEventHandler(MyButton_MouseLeave);
    this.MouseLeftButtonDown += new MouseButtonEventHandler(MyButton_MouseLeftButtonDown);
    this.MouseLeftButtonUp += new MouseButtonEventHandler(MyButton_MouseLeftButtonUp);
}

void MyButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    _mousePressed = true;
}
void MyButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    if (_mousePressed)
    {
        OnClick();
    }
    _mousePressed = false;
}

void MyButton_MouseLeave(object sender, MouseEventArgs e)
{
    _mousePressed = false;
}

protected void OnClick(MouseEventArgs e)
{
    if (Click != null)
        Click(this, e);
} 

public event EventHandler<MouseEventArgs> Click;

This code is the final code for the control for this part of the tutorial. I know that the idea of setting the _mousePressed to false on MouseLeave stinks, but it's the simple way of handling it. It should probablybe handled a bit more logical, but that isn't really the goal of this blog post. The post is about the mechanics of creating a control, not about how to build a good button. There is already a button in the framework, so the control is not really useful anyway...
To see that it works, compile the project and add a click handler to the MyButton control in the Silverlight Applications Page.xaml. Something like this:

Page.xaml

<btn:MyButton Width="100" Height="50" Text="Hello World" Background="Yellow" Foreground="White" Click="MyButton_Click" />

Page.xaml.cs

private void MyButton_Click(object sender, MouseEventArgs e)
{
    MyButton.MyButton btn = (MyButton.MyButton)sender;
    btn.Text = "Hello again";
}

The next part will be about the parts and states model. Mostly about states, but parts will be mentioned. This control doesn't have any parts which makes it a bit hard to talk about, but it will have some states added to it so that the look can be changed when the mouse is over, the mousebutton is pressed and so on. Stay tuned for that...

XAML 파일을 열 때, 미리보기 화면과 코드 부분으로 나누어 열리는 것을 볼 수 있습니다.
Visual Studio 설치시, 나누어 보기가 디폴트 설정으로 되어 있을 것입니다.
나누어 보기 함으로써, 개발 PC 리소스를 마니 잡아, 버벅대는 현상을 볼수 있는데,
아래와 같은 옵션 설정으로 코드 보기만 함으로써 좀 더 쾌적한 환경에서 개발할 수 있습니다.

Visual Studio 상단 메뉴에 [도구]를 클릭하면, 옵션이라는 항목이 보입니다.



텍스트 편집기 - XAML 항목이 보이는데, 기타를 클릭하시면 기본 보기 옵션이 나타납니다.


"항상 전체 XAML 뷰에서 문서 열기" 라는 항목을 체크하시면 옵션 설정은 끝납니다.

1.     .NET Framework 3.5 SP1 설치

  -     Visual Studio 2008 설치 시, .NET Framework 3.5 설치가 같이 되지만, Visual Studio 설치 전에 .NET Framework 3.5 SP1를 먼저 설치 하시는게 좋습니다. (풀 버전 : 다운로드 / 온라인 버전 : 다운로드)

2.     Visual Studio 2008 설치

  -     반드시 서비스팩 1을 설치해야 합니다. (서비스팩1 : 다운로드)

3.     Expression Blend2 설치

  -     Blend2.5 버전과 같은 이전 버전은 삭제 후, 설치 해야 합니다.

  -     Expression Blend2 설치 후, 서비스팩1을 설치해 주셔야 합니다. (Expression Blend2 Trial : 다운로드 / 서비스팩1 : 다운로드)

  -     참고 사이트 : Expression 공식 사이트

4.     Microsoft Silverlight Tools for Visual Studio 2008 SP1 설치

  -     Visual Studio 2008 SP1에서 Silverlight 프로젝트를 구성하고 수행할 수 있도록 하는 필수 프로그램입니다. (Silverlight 프로젝트 템플릿 파일 설치)

  -     Visual Studio 2008 Langeuge에 맟춰 설치해야 합니다. (다운로드)

5.     Silverlight Plug-In 설치

  -     Flash를 보기 위해 ActiveX컨트롤을 설치하는 것과 같이 Silverlight를 보기 위한 Plug-In를 설치해야 합니다. (다운로드)

 

6.     Silverlight SDK 

    -     Silverlight 2.0 소프트웨어 개발 키드 설명서 (다운로드)

7.     Deep Zoom Composer 설치

    -     대용량 이미지를 줌 형태로 볼 수 있도록 구성해주는 툴입니다. (샘플 사이트1, 샘플 사이트2 / 다운로드)

 

8.     Silverlight Toolkit 설치

  -     Silverlight에 다양한 확장 컨트롤을 사용할 수 있습니다. (Toolkit 샘플 사이트 / 다운로드)

 

  ※     1~5번까지는 필수 사항이며, 6~8번은 선택 사항입니다.

 

I was teaching someone about DependencyProperty the other day when I realized two things: first, DependencyProperty is a bad name.  Does anyone know what they depend on (DependencyObject doesn’t count)?  The dependency thing makes them sound kind of wimpy when, in fact, they’re actually awesome.  Going forward, you can use the terms DependencyProperty and SuperProperty interchangeably with me.

Second, I realized that there isn’t a good built in snippet for Silverlight DPs (or SPs). At least not one that I know of.  And without a snippet, whose going to bother to create a DP.  Seriously, it would be a pain.

I’m a snippet enthusiast or, dare I say, a fanatic.  It’s because I think that a good snippet library makes for good code: it makes your code consistent and it encourages you to do things the “right” way even if “right” requires a lot of code.

I’m such a snippet fan, in fact, that I devoted my talk at MIX last year to the subject.  Unfortunately, a lot of the snippets from the talk have grown stale.  In the meantime, I’ve been building out a new library.  For WPF, I use Dr. WPF’s indispensible snippets.  For Silverlight, I’ve slowly been building up a new library.  Since I haven’t been able to find much else out there, I thought I’d share mine.

Download The Snippets

If you’re new to VS snippets, you can get the skinny here.  To use them you just install the .vsi (see link above) and then type the snippet shortcut (e.g. sldp), hit tab a couple of times to kick off the snippet and then start providing the values it asks for (hit tab to move between values).  When you’re done, hit enter.

The Short Descriptions

Here’s the short guide to the snippets.  A little more detail follows.

sldp A basic Silverlight DependencyProperty
sldpc A Silverlight DependencyProperty with a property changed callback
sldpa An attached Silverlight Dependency Property
slevent A really basic event definition
slsbc An inline, anonymous Storyboard.Completed handler (check this one out, it’s cooler than it sounds)
slpanel A really simple implementation of a panel
slrss A super basic collection class that loads an RSS feed
slvc The boilerplate code you need to create a value converter

The Long Descriptions

sldp

This snippet creates the most basic DependencyProperty (i.e. SuperProperty) possible (well, most basic possible that is still useful).  You just specific the name, the type and the default value. Helfpul hint: remember that when you provide a default value for a double, you need to include the decimal (like in 0.0 instead of just 0) so that the compiler treats is like a double and not an int.

#region MyProperty (DependencyProperty)

/// <summary>
/// A description of the property.
/// </summary>
public int MyProperty
{
    get { return (int)GetValue(MyPropertyProperty); }
    set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
    DependencyProperty.Register("MyProperty", typeof(int), typeof(Page),
      new PropertyMetadata(0));

#endregion

sldpc

This is a DependencyProperty that includes a a place to handle the associated property changed notifications (and a protected method in case subclasses also want to know about the change).

#region MyChangingProperty (DependencyProperty)

/// <summary>
/// A description of the property.
/// </summary>
public int MyChangingProperty
{
    get { return (int)GetValue(MyChangingPropertyProperty); }
    set { SetValue(MyChangingPropertyProperty, value); }
}
public static readonly DependencyProperty MyChangingPropertyProperty =
    DependencyProperty.Register("MyChangingProperty", typeof(int), typeof(Page),
    new PropertyMetadata(0, new PropertyChangedCallback(OnMyChangingPropertyChanged)));

private static void OnMyChangingPropertyChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    ((Page)d).OnMyChangingPropertyChanged(e);
}

protected virtual void OnMyChangingPropertyChanged(DependencyPropertyChangedEventArgs e)
{
}

#endregion

sldpa

This is a snippet for an attached DependencyProperty.  That means that this value can be set on any element (e.g. Canvas.Left is an attached property defined by canvas to be set on other elements).  This also has property changed handler, but notice that its a static.  That’s because the change could happen on any object so you’ll want to pay attention to the DependencyObject that gets passed in to the handler.  That’s the object where the property was changed.

#region MyAttachedProperty (Attached DependencyProperty)

public static readonly DependencyProperty MyAttachedPropertyProperty =
    DependencyProperty.RegisterAttached("MyAttachedProperty", typeof(double), typeof(Page),
    new PropertyMetadata(new PropertyChangedCallback(OnMyAttachedPropertyChanged)));

public static void SetMyAttachedProperty(DependencyObject o, double value)
{
    o.SetValue(MyAttachedPropertyProperty, value);
}

public static double GetMyAttachedProperty(DependencyObject o)
{
    return (double)o.GetValue(MyAttachedPropertyProperty);
}

private static void OnMyAttachedPropertyChanged(DependencyObject o,
    DependencyPropertyChangedEventArgs e)
{

}

#endregion

slevent

This snippet creates a really simple event.  I use the RoutedEvent types even though (as far as I know) they aren’t actually routed.  There may be a better practice here, but this meets my needs 90% of the time.  Feel free to let me know if you have a better suggestion.

#region SomethingChanged

/// <summary>
/// A description of the event.
/// </summary>
public event RoutedEventHandler SomethingChanged;

private void RaiseSomethingChanged()
{
    if (SomethingChanged != null)
    {
        SomethingChanged(this, new RoutedEventArgs());
    }
}

#endregion

slsbc

This snippet creates an inline completed event handler for a storyboard using an anonymous delegate. I like this approach for handing completed events because it makes it straightforward to remove the handler (thanks to Dr. WPF for that suggestion) and it allows you to access local variables from within the handler.  I haven’t found a clean way to do either of those things any other way, so I end up writing most of my Storyboard event handlers this way.   By the way, the delegate has to be declared first like this so that we have a reference to it (to remove it) from within the handler.

// using an anonymous, inline handler for the completed event allows you to use 
// local variables from within the event handler and keeps our code cleaner

EventHandler handler = null;
handler = delegate(object s, EventArgs e)
{
    // remove the handler 
    MyStoryboard.Completed -= handler;

    // code to execute when the storyboard is completed goes here
};

MyStoryboard.Completed += handler;
MyStoryboard.Begin();

Note: The rest of these snippets are much longer so I’m not going to include them inline.  You’ll have to download them to check them out.

slpanel

This snippet creates an entire panel, including very very basic implementations of MeasureOverride and ArrangeOverride.  To be clear, this is not a robust panel implementation.  MeasureOverride, in particular, is not going to give you the results you probably want in advanced scenarios (like inside of a ScrollViewer).  But this will get you started.  It’s gets the boilerplate out of the way and lets you start partying on the layout logic really quickly.

slvc

This snippet creates a shell for a ValueConverter which you can use in conjunction with a binding to modify a value during the binding process.  It’s an entire class (like the panel) so you’ll want to make sure you use it in the right spot in your file.

slrss

This snippet creates a very simple collection out of the items in an RSS feed.  You provide the URL and this does the rest.  It’s a great way to quickly get up and running with a databinding scenario.  I mostly use this for training, etc.  It’s also a simple example of how to parse an XML feed using LINQ and how to load a file using the async web request stuff.  Oh, and it includes the content for a simple crossdomain.xml policy file.  It’s kind of a grab bag but I kept it around bits of it from time to time.

I'll be using this page to link to Silverlight 2 articles and posts (both ones I write as well ones by others).  Check back often for updates as I'm just getting started.

Getting Started Tutorials

  • Silverlight 2 End to End Tutorial: Digg Sample: I recommend reading this post first if you are looking to get started with Silverlight 2 development for the first time.  It not only describes the high-level of what Silverlight 2 provides, but also links to a series of 8 blog posts that walkthrough building a Digg client sample application.  These tutorial posts provide a good introduction to Silverlight 2 and WPF development concepts. 

  • First Look at Using Expression Blend with Silverlight 2: I recommend downloading the Expression Blend 2.5 March preview and following along with this tutorial post.  I think it does a good job of showing off some of the common features of Expression Blend, and uses it to build an IM chat client sample.

  • Jesse Liberty Silverlight Tutorials: Jesse Liberty is writing an in-depth series of articles that cover Silverlight programming concepts in more depth.  Bookmark this page and check back frequently to read them as they get published (he also has PDF versions of each article that you can download and read offline).

Getting Started Presentations

  • My Getting Started with Silverlight Talk: You can download the slides + samples from my "Getting Started with Silverlight 2" talk that I recently gave in Arizona.  Feel free to re-use the slides however you want for your own presentations.

Documentation Links

Tutorials and Samples

  • Using Deep Zoom with Silverlight 2: Jacek Ciereszko has a nice blog post that describes how to use the "Deep Zoom" feature of Silverlight 2 to implement image zoom functionality like with the Hard Rock sample.

Deployment

User Controls

ListBox and ScrollViewer

DataGrid

  • Using Silverlight 2's DataGrid with WCF + LINQ to SQL: This 15 minute video blog demonstrates how to build a LINQ to SQL object model on the server and publish it using WCF.  It then demonstrates how to build a Silverlight client that uses the new Silverlight DataGrid control, and which calls the WCF service to retrieve the LINQ to SQL data to populate it with.

  • Simple Editing of Web Service Data in a DataGrid: Mike Taulty has a nice blog post that shows how to create a WCF service on the server, and then use it from a Silverlight 2 client to retrieve data, bind it to a DataGrid, allow users to update rows, add/delete rows, and then save it back to the server using Silverlight 2 Beta1.

  • Sorting with Silverlight 2's DataGrid Control: The DataGrid control in Silverlight 2 Beta1 doesn't yet have built-in column sorting support (it is coming in Beta2).  That hasn't stopped Matt Berseth though!  In this post he shows how to implement sorting using a custom header column approach.  Also check out Matt's post here, which provides a DataGrid test page that shows off a number of the current DataGrid features.

Control Templates

Web Services and Networking

  • Web Services and Silverlight: This helpful post discusses how to use web-services with Silverlight and links to relevant quickstart samples and documentation.

HTML Integration

  • Silverlight Interop with HTML: Wilco Bauwer has a great post on the HTML and JavaScript integration features enabled by Silverlight 2 (he should know all about them - since he was the developer who built them!).

Unit Testing

  • Unit Testing with Silverlight 2: This post of mine talks about how to unit-test Silverlight 2 applications using the unit test framework in the Silverlight SDK.

Video Scenarios

Accessibility and 508 Compliance

  • Accessibility in Silverlight 2: Mark Rideout from the Silverlight team talks about accessibility support with Silverlight 2, and talks about how you'll be able to build section 508 and accessible solutions using Silverlight.


목차

소개

몇 주 전부터 실버라이트를 사용하기 시작했데, 정말 놀랍다. WPF의 강력한 표현력과 C#의 능력을 겸한 실버라이트는 정말 강력한 툴이 아닐까 생각해본다. 이 포스팅에서는 데이터베이스로 부터 데이터를 가져와서 실버라이트 응용 프로그램에 적용시키는 것을 다룰것이다. 실버라이트와 ASP.NET의 공통점에 대해서도 다룰것이기 때문에 ASP.NET 개발을 하고 있는 개발자이고 실버라이트에 대해 알고 싶은 개발자또한 이 포스팅을 보는것을 추천한다.



다룰 내용

이 포스팅을 통해 비지니스 어플리케이션을 제작하고자 한다. 비지니스 어플리케이션은 대부분 데이터(데이터베이스에서 가져온)를 다루기 때문에, 실버라이트에서 데이터가 어떻게 표현되는지를 살펴보기로 하자. Northwind 데이터베이스를 예제로 한다. LINQ 클래스를 생성하고, WCF 서비스를 사용하여 데이터를 받아올것이고, 최종적으로 리스트박스와 데이터그리드(데이터템플릿이 딸린)에 뿌려보기로 하자. 우리가 만들 UI는 고객, 주문, 주문 상세가 있는 마스터-디테일 형태가 될것이다. 그리고 이 포스팅에 소개되는 코드는 실버라이트2 RC1 을 기준으로 작성되었다. (역주:번역하면서 한글 실버라이트2 정식으로 테스트 하였습니다.)



예제 프로그램의 실행

예제 프로젝트를 실행하기위해 조금 해야할것이 있다. 기본적으로 솔루션에는 시작 프로젝트가 없기때문에, 솔루션의 설정 윈도우에서 수작업으로 DataApplication.Web 을 시작 프로젝트로 설정해 준다. (역주:이부분은 뭔가 이해가 안되는데, 제공되는 솔루션에는 이미 DataApplication.Web이 시작 프로젝트로 설정되어 있다.)



ASP.NET 프로그래머가 알아둘것

ASP.NET 프로그래머라면, ASP.NET 과는 다르게 실버라이트는 서버가 아닌 클라이언트에서 C#코드가 실행된다는것을 알아두자. 클라이언트라는 말에 자바스크립트라고 생각할 수도 있는데, 아니다. 실버라이트로 코딩함에 있어서 가장 좋은 방법은 자바스크립트를 사용하지 않고 클라이언트 코딩(C#)으로 모두 다 다루는것이다. (적어도 나는 자바스크립트를 잘 다루지 못하니깐)



왜 WCF를 사용하나? 그냥 DB에 접근하면 안되나?

음, 간단하게 말하자면 C# 코드가 클라이언트에서 돌아가고, 클라이언트에서는 데이터베이스에 직접 연결할 수 없기때문이다. 우리의 실버라이트 프로젝트에는 DataSet이나 DataSource 같은 것이 없다는것을 알아두자. 그리고 System.Data 네임스페이스에는 즐겨쓰던 클래스들이 빠져있다. 하지만, WCF 서비스 같은 좋은 것들이 포함되어 있기도 하다. 이 포스팅은 WCF 서비스를 통해 데이터를 가져오는 방법을 보여준다.



시작해보자

비주얼 스튜디오로 실버라이트 어플리케이션을 만들기 위해, 이곳에서 실버라이트 툴을 다운로드받아서 설치하자. 설치후에 Silverlight 응용 프로그램과 Sliverlight 클래스 라이브러리가 추가된것을 볼 수 있다. 여기서 "Silverlight 응용 프로그램" 을 선택하고 이름을 DataApplication 으로하여 시작하자.

사용자 삽입 이미지

확인하면 실버라이트 응용프로그램을 호스팅할 방법을 선택해야되는데, "ASP.NET 웹 응용프로젝트"를 선택하여 ASP.NET 서버측에서 LINQ 클래스와 WCF서비스를 만들 수 있도록 하자.

사용자 삽입 이미지
여기까지 제대로 했다면 두개의 프로젝트가 추가된 솔루션이 열린다. 그중 DataApplication 프로젝트는 클라이언트에서 돌아갈 실버라이트 프로젝트이고, DataApplication.Web 프로젝트는 서버측의 ASP.NET 프로젝트다.



LINQ 클래스 생성

Northwind 데이터베이스는 가벼우면서도 여러가지 상황에 잘 맞기때문에 쓸모가 많다. (여기서 받을 수 있다.) MDF 파일에는 SQLExpress로 접근할 수 있는 프로젝트도 포함되어있다. LINQ 데이터 클래스를 생성하기위해 ASP.NET 응용 프로그램에서 LINQ to SQL 클래스 추가를 선택하도록 하자. (역주:instNwnd.sql 파일만 실행시켜서 DB생성해도 무관하다.)

사용자 삽입 이미지
이제 서버탐색기에서 Northwind 데이터베이스로 새로운 연결을 하나 만들고(SQLExpress든 SQL Server든 상관없다. 자신이 가진걸로 하자.), Customers, Orders, OrderDetails 테이블을 LINQ 디자이너로 드래그하자. 여기서 중요한 점은 실버라이트 응용프로그램으로 데이터를 전송할 수 있도록 하기 위해서는 LINQ로 생성되는 데이터의 직렬화가 가능하도록 해주어야 한다는것이다. 직렬화가 가능하도록 설정하기 위해서 LINQ 디자이너의 빈공간을 클릭한 후 속성창에서 직렬화모드(Serialization Mode)를 Unidirectional 로 설정하도록 하자.

사용자 삽입 이미지



실버라이트 WCF 서비스 생성

이제 데이터를 받을 수 있는 서비스를 추가하자. 실버라이트2 베타2 이전에는 WCF 서비스를 사용하기 위해서는 몇가지 꼼수가 필요했었다. 그러나 다행히도 실버라이트2 베타2 이상의 버전에서는 "Silverlight 사용 WCF 서비스" 라는 템플릿이 제공된다. 자, 새항목추가 - Silverlight를 선택하고 DataService 라는 이름으로 "Sliverlight 사용 WCF 서비스"를 ASP.NET 프로젝트에 추가하자.

사용자 삽입 이미지
생성한 서비스에 세가지 메서드를 작성할건데, 하나는 모든 고객을 리턴하는것이고, 다른 하나는 한 고객의 주문을 리턴하는것이며, 나머지 하나는 특정 주문의 주문상세내역을 리턴해주는것이다. 이 메서드는 반드시 [OperationContract] 특성이 있다는것을 알아두자. (이 특성은 ASMX 서비스의 [WebMethod]와 유사하다.) 아주 간단한 LINQ를 이용해 데이터를 가져오는 것을 구현하였다. 아래 코드를 DataService.svc.cs 파일에 추가하자. (역주:이후 나오는 코드중 Customer, Order, OrderDetail 테이블은 실제 모두 마지막에 's'가 붙어있다. 원저자의 테이블과 약간 틀린듯하다. 번역본에 소개되는 코드에는 모두 's' 를 붙여 놓았다.)

  1. [OperationContract]   
  2. public List<Customers> GetCustomers()   
  3. {   
  4.     DataClasses1DataContext datacontext = new DataClasses1DataContext();   
  5.     return datacontext.Customers.ToList();   
  6. }   
  7.   
  8. [OperationContract]   
  9. public List<Orders> GetOrders(string customerID)   
  10. {   
  11.     DataClasses1DataContext datacontext = new DataClasses1DataContext();   
  12.     return (from order in datacontext.Orders   
  13.             where order.CustomerID == customerID   
  14.             select order).ToList();   
  15. }   
  16.   
  17. [OperationContract]   
  18. public List<Order_Details> GetOrderDetails(int orderID)   
  19. {   
  20.     DataClasses1DataContext datacontext = new DataClasses1DataContext();   
  21.     return (from orderdetail in datacontext.Order_Details   
  22.             where orderdetail.OrderID == orderID   
  23.             select orderdetail).ToList();   
  24. }  



실버라이트 프로젝트에 서비스 참조 추가

서버측의 ASP.NET 프로젝트에서 할일은 이걸로 끝이다. 데이터베이스에서 데이터를 얻기 위해 LINQ 클래스를 만들었고, 이 LINQ 객체를 전달해주기 위해서 WCF 서비스를 만들었다. 이제, 클라이언트측의 실버라이트에서 데이터를 사용할 준비가 끝난 셈이다. 이제 실제 사용을 위해 실버라이트 프로젝트인 DataApplication 프로젝트에 서비스 참조를 추가하자. 참조 - 서비스 참조 추가를 실행하고 팝업창에서 검색버튼을 누르면 방금 우리가 만들었던 WCF 서비스를 검색해준다.

사용자 삽입 이미지



UI 만들기

실버라이트 페이지, 컨트롤은 레이아웃을 담당하는 XAML 파일과 xaml.cs 파일의 코드 비하인드 파일로 구성된다. 간단하게보면 UI를 구성하기위한 *.aspx 파일과 각종 처리로직과 이벤트 핸들러 등이 있는 *.aspx.cs 파일로 구성되는 ASP.NET 과 유사하다. 자, 우리의 응용 프로그램에도 기본적인 레이아웃을 구성해 보자.



DataGrid 컨트롤을 사용하기위해 어셈블리 추가

데이터를 보여주기위해서 DataGrid 컨트롤을 사용하려고 하는데, 실버라이트에는 기본적으로 DataGrid 컨트롤의 참조가 추가되어 있지 않다. 직접 추가해주자. 이 과정은 ASP.NET 에서 사용자정의 컨트롤을 추가하는 방법과 거의 같다. DLL 참조를 추가하고 aspx 페이지에 태그를 등록했던것을 상기해보도록 하자. 실버라이트에서는 참조 - 참조추가를 클릭하고 목록에서 System.Windows.Controls.Data 를 선택하기만 하면 된다. (이 어셈블리에 DataGrid 컨트롤이 포함되어 있다.)

사용자 삽입 이미지
참조를 추가한 다음, XAML 코드에 추가한 참조의 네임스페이스를 등록해 주어야한다. 아래의 네임스페이스 선언을 Page.xaml 파일에 추가하자.

  1. xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"  

사용자 삽입 이미지



UI 레이아웃

이제 우리가 할것은, LayoutRoot 라는 이름의 Grid 를 만들고 3개의 행을 가지도록 한다. 첫번째 행은 어플리케이션 제목을 위해 사용한다.(width=50) 세번째 행은 상태바를 위해 사용한다.(width=20) 그리고 가운데 행은 메인데이터를 위해 사용한다. (width=*, 남는 공간을 전부 사용) LayoutRoot 그리드의 첫번째 행에 TextBlock를 추가하고 마지막 행에 txtStatus 라는 이름의 빈 TextBlock을 추가한다. LayoutRoot 그리드의 가운데 행(컨텐트 홀더로 사용하기로 한)에 ContentRoot 라는 이름의 2행 2열로 구성된 또 다른 그리드를 추가한다. 왼쪽 열은 너비 200이고 오른쪽은 나머지 전체를 차지 하도록 하고, 행들은 각각 60%, 40%의 비율을 가지도록 한다. ContentRoot 그리드의 왼쪽 열에는 ListBox 를 아래 행까지 합쳐서 배치하고, 오른쪽 열에는 위, 아래 행 두곳 각각의 DataGrid 를 배치하는데, 위쪽은 주문 데이터를 위한 그리드이고, 아래쪽은 주문상세를 위한 그리드이다. 말로 줄줄 풀어써서 짜증났다면 이 문장들을 XAML화한 코드가 아래에 있다. Page.xaml 파일에 추가하도록 하자.

  1. <UserControl xmlns:basics="clr-namespace:System.Windows.Controls;   
  2.     assembly=System.Windows.Controls"    
  3.     x:Class="DataApplication.Page"  
  4.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  5.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  6.     xmlns:data="clr-namespace:System.Windows.Controls;   
  7.         assembly=System.Windows.Controls.Data"   
  8.     Width="Auto" Height="Auto">   
  9.     <Grid x:Name="LayoutRoot" Background="White">   
  10.         <Grid.RowDefinitions>   
  11.             <RowDefinition Height="55" x:Name="HeaderRow" />   
  12.             <RowDefinition Height="*" x:Name="ContentRow"/>   
  13.             <RowDefinition Height="20" x:Name="FooterRow"/>   
  14.         </Grid.RowDefinitions>   
  15.         <Grid.ColumnDefinitions>   
  16.             <ColumnDefinition Width="*" />   
  17.         </Grid.ColumnDefinitions>   
  18.   
  19.         <!-- Heading -->   
  20.         <TextBlock x:Name="txtHeader" Grid.Row="0"    
  21.                    FontSize="20" Margin="5,5" Foreground="Blue"  
  22.                    Text="My First Data Application in Silverlight">   
  23.         </TextBlock>   
  24.   
  25.         <!-- A textblock in the footer to be used as an Status bar -->   
  26.         <TextBlock x:Name="txtStatus" Grid.Row="2"    
  27.                FontSize="10" Margin="5,0" Foreground="Red">   
  28.         </TextBlock>   
  29.   
  30.         <!-- Content Holder -->   
  31.         <Grid x:Name="ContentGrid" Grid.Row="1" Margin="5">   
  32.             <Grid.RowDefinitions>   
  33.                 <RowDefinition Height=".6*" />   
  34.                 <RowDefinition Height=".4*" />   
  35.             </Grid.RowDefinitions>   
  36.             <Grid.ColumnDefinitions>   
  37.                 <ColumnDefinition Width="200" />   
  38.                 <ColumnDefinition Width="*" />   
  39.             </Grid.ColumnDefinitions>   
  40.   
  41.             <!-- Listbox for displaying customers -->   
  42.             <ListBox x:Name="lstCustomers" Grid.Column="0" Grid.RowSpan="2"  
  43.                      DisplayMemberPath="ContactName"  
  44.                      Loaded="lstCustomers_Loaded"  
  45.                      SelectionChanged="lstCustomers_SelectionChanged">   
  46.             </ListBox>   
  47.   
  48.             <!-- DataGrid for displaying orders of a customer    
  49.                 (with autogenerated columns) -->   
  50.             <data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1"    
  51.                            AutoGenerateColumns="True"  
  52.                            SelectionChanged="dgOrders_SelectionChanged">   
  53.             </data:DataGrid>   
  54.   
  55.             <!-- DataGrid for displaying orderdetais for an order -->   
  56.             <data:DataGrid x:Name="dgOrderDetails" Grid.Row="1" Grid.Column="1"    
  57.                            AutoGenerateColumns="True"  
  58.                            AutoGeneratingColumn="dgOrderDetails_AutoGeneratingColumn">   
  59.             </data:DataGrid>   
  60.   
  61.         </Grid>   
  62.   
  63.     </Grid>   
  64. </UserControl>  

WPF 의 레이아웃에 관해서는 코드 프로젝트와 여러 다른 사이트에서 많이 다루고 있기 때문에 굳이 자세히 설명하지는 않겠다.(Sacha Barber의 이런 것 이 있다.) 우리는 ListBox와 DataGrid에 대해 훓어 보는 편이 낮다.



ListBox

lstCustomers 라고 명명한 리스트박스는 데이터베이스의 고객을을 보여주기위해 사용한다. Loaded 이벤트를 등록해서 바인딩을 할것이다. 리스트박스는 object 소스가 바인딩되었을때 각 items 컬렉션의 object.ToString() 을 해당 값으로 보여준다는것을 알아두자. 다른 값을 보여주려면 세가지 방법을사용할수 있다.

  • object.ToString() 메서드를 오버라이드 한다. (이 포스팅에서는 이렇게 구현하지 않는다.)
  • Data Template 를 정의한다. (이 방법이 제일 유연한 접근방법이다. 나중에 DataGrid의 컬럼을 직접 정의하면서 간단하게 살펴보겠지만, 지금은 이 방법을 사용하지 않는다.)
  • ListBox의 DisplayMemberPath 프러퍼티에 보여줄 객체의 프러퍼티를 설정한다. (아주 간단한 방법이다. 지금, 이 방법으로 구현해 보겠다.)

ListBox에 바인딩된 Customer 객체의 ContactName 프러퍼티를 보여줄 것이기 때문에, DisplayMemberPath = "ContactName" 라고 설정한다. 또한, DataGrid에 선택된 고객의 주문을 보여줄수 있도록 하기위해 SelectionChanged 이벤트도 등록해 주도록 하자.



DataGrids

이제는 DataGrid 를 설정하는것이 좀더 수월해졌다. 바인딘된 데이터에 따라서 자동으로 컬럼을 생성하도록 설정하면 되기 때문이다. 그리고, dgOrderDetails 그리드에 AutoGeneratingColumns 이벤트를 등록해주면 컬럼이 자동으로 생성될때 필요치 않는 컬럼을 제거하는 로직을 구현할 수 있는데, 이렇게 하는것이 특정 칼럼을 수월하게 제거하는 일반적인 방법이다. 그리고 자동생성말고 직접 컬럼을 설정하는것은 나중에 다시 알아보기로 하고, 일단은 자동생성으로 간단하게 구현해 보자.



코드 작성

ListBox 처리

ListBox에는 lstCustomers 라고 명명하고, Loaded 이벤트에서 우리가 보여줄 고객에 대한 정보를 보여주자. 실버라이트의 대부분의 서비스 호출은 비동기적으로 일어날 수 있기때문에 데이터가 바인딩되는 시점에 콜백함수를 등록하여 데이터 바인딩 결과에 대한 처리를 해주면 좋다. 여기서는 데이터 로딩과 관련된 메세지를 텍스트로 뿌려줄것이다. (처음에 LayoutGrid의 마지막행에 만들어 두었던 txtStatus 라는 이름의 텍스트 박스를 사용하기로 한다.)

  1. private void lstCustomers_Loaded(object sender, RoutedEventArgs e)   
  2. {   
  3.     DataServiceClient svc = new DataServiceClient();   
  4.     this.txtStatus.Text = "Loading customers...";   
  5.     svc.GetCustomersCompleted += new  
  6.       EventHandler<GetCustomersCompletedEventArgs>(svc_GetCustomersCompleted);   
  7.     svc.GetCustomersAsync();   
  8. }   
  9.   
  10. void svc_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)   
  11. {   
  12.     if (e.Error == null)   
  13.     {   
  14.         this.lstCustomers.ItemsSource = e.Result;   
  15.         this.txtStatus.Text = string.Empty;   
  16.     }   
  17.     else  
  18.     {   
  19.         this.txtStatus.Text =   
  20.             "Error occurred while loading customers from database";   
  21.     }   
  22. }  



고객에 대한 주문 보여주기

이제, ListBox에서 특정 고객을 선택하면 해당 고객에 대한 주문건을 보여주는 코드를 작성해 보자. ListBox의 SelectionChanged 이벤트 핸들러에서 아까 만들어 두었던 WCF 서비스를 호출하여 데이터를 가져오고, 이를 dgOrders 그리드에 바인딩시켜서 보여주자. 이번 구현에서는 간단한 구현을 위해 익명메서드를 사용해보기로 하자.

  1. private void lstCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)   
  2. {   
  3.     Customers selectedCustomer = this.lstCustomers.SelectedItem as Customers;   
  4.     if (selectedCustomer != null)   
  5.     {   
  6.         DataServiceClient svc = new DataServiceClient();   
  7.         this.txtStatus.Text = "Loading orders...";   
  8.         svc.GetOrdersCompleted +=   
  9.             delegate(object eventSender, GetOrdersCompletedEventArgs eventArgs)   
  10.             {   
  11.                 if (eventArgs.Error == null)   
  12.                 {   
  13.                     this.dgOrders.ItemsSource = eventArgs.Result;   
  14.                     this.txtStatus.Text = string.Empty;   
  15.                 }   
  16.                 else  
  17.                 {   
  18.                     this.txtStatus.Text =   
  19.                         "Error occurred while loading orders from database";   
  20.                 }   
  21.             };   
  22.         svc.GetOrdersAsync(selectedCustomer.CustomerID);   
  23.     }   
  24. }  



주문에 대한 상세주문정보 보여주기

ListBox의 SelectionChanged 이벤트와 유사하게 dgOrders에도 SelectionChanged 이벤트를 등록하고 코드를 작성하자. 이번에는 람다 표현식을 사용해서 구현해 보겠다.

  1. private void dgOrders_SelectionChanged(object sender, EventArgs e)   
  2. {   
  3.     Orders selectedOrder = this.dgOrders.SelectedItem as Orders;   
  4.     if (selectedOrder != null)   
  5.     {   
  6.         DataServiceClient svc = new DataServiceClient();   
  7.         this.txtStatus.Text = "Loading order details...";   
  8.         svc.GetOrderDetailsCompleted +=   
  9.             (eventSender, eventArgs) =>   
  10.             {   
  11.                 if (eventArgs.Error == null)   
  12.                 {   
  13.                     this.dgOrderDetails.ItemsSource = eventArgs.Result;   
  14.                     this.txtStatus.Text = string.Empty;   
  15.                 }   
  16.                 else  
  17.                 {   
  18.                     this.txtStatus.Text =   
  19.                         "Error occurred while loading order details from database";   
  20.                 }   
  21.             };   
  22.         svc.GetOrderDetailsAsync(selectedOrder.OrderID);   
  23.     }   
  24. }  



dgOrderDetails 에서 일부 자동생성된 칼럼 제거하기

아까 작성한 XAML에서 우리는 DataGrid의 AutoGenerateColumns 속성을 true 로 설정함으로써 바인딩된 데이터에 따라서 컬럼을 생성하도록 하였다. 이제 이렇게 생성된 컬럼중에서 dgOrderDetails 그리드의 OrderID 컬럼을 제거해보자. 아까 말했다시피 AutoGeneratingColumns 이벤트 핸들러를 통해서 가능하다.

  1. private void dgOrderDetails_AutoGeneratingColumn(object sender,   
  2.     DataGridAutoGeneratingColumnEventArgs e)   
  3. {   
  4.     if (e.Column.Header.ToString() == "OrderID")   
  5.         e.Column.Visibility = Visibility.Collapsed;   
  6. }  



중간점검.. 프로젝트 실행

어느정도 볼만한 프로그램이 만들어진것 같다. 실행해서 이것저것 마구 건드려보자. 고객, 주문, 주문상세 셋의 선택이 바뀔때 연관되어 바뀌는 관계도 보고, 그리드 자체의 수정기능, 컬럼헤더클릭으로 정렬기능, 헤더의 컬럼 사이즈 변경, 브라우저 크기에 따른 동적 크기 계산 등등.. 원하는건 뭐든지 해보라. 멋지지 않은가? 자, 이제 DataGrid 의 컬럼과 템플릿에 대해 알아보자.

사용자 삽입 이미지



컬럼 정의

실버라이트의 컬럼 설정방법은 ASP.NET의 방법과 유사하다. DataGrid는 세가지 종류의 컬럼을 설정할 수 있다.

  • DataGridTextBoxColumn - 보여주기만 하거나, 수정도 가능한 TextBlock 형태의 데이터. 바인딩된 객체의 어떤 프러퍼티를 보여줄것인지를 DisplayMemberPath 를 통해 설정해줘야 한다.
  • DataGridCheckBoxColumn - 불린, 널러블불린값의 체크박스의 형태. 읽기전용으로 설정하거나, 수정할 수 있도록 설정할 수도 있다.
  • DataGridTemplateColumn - ASP.NET의 TemplateColumn 처럼 강력한 기능을 제공해준다. DataTemplate 을 정의해서 원하는 컨트롤을 해당 칼럼에 설정할 수있다. DataTemplate에 대한 좀 더 자세한 내용은 MSDN 의 이곳에서 확인하도록 하자.

칼럼들에대해 좀더 자세히 알고 싶으면 Scott Morris 의 블로그를 방문해보자. 꽤 좋은 포스팅을 해놓았다.

자, 아제 실제 응용프로그램에 적용해 보도록하자. 간단한 구현을 위해 네개의 칼럼만 정의하겠다. OrderID, EmployeeID 에는 DataGridTextBoxColumn을 사용하고, OrderDate에는 DataGridTemplateColumn 을 사용할건데, CellTemplate 에는 TextBlock을, CellEditingTemplate에는 DatePicker를 사용해보자. 아, 마지막으로 Frieght 칼럼에도 TemplateColumn을 사용하는데 거기에는 데이터 표현을 위해 TextBlock을 넣고 값 수정을 위해 Slider를 넣어서 값의 증감을 조절할 수 있도록 하겠다. 근데, DataTemplate 에는 하나의 컨트롤만 넣을 수 있도록 되어 있으므로, StackPanel을 만들어 이 패널에 TextBlock과 Slider를 넣은뒤에 DataTemplate에 넣기로 하자.

변경된 dgOrders 부분의 코드는 아래와 같다.

  1. <!-- DataGrid for displaying orders of a customer -->   
  2. <data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1"    
  3.    AutoGenerateColumns="False"  
  4.    SelectionChanged="dgOrders_SelectionChanged">   
  5.     <data:DataGrid.Columns>   
  6.         <!-- OrderID text column -->   
  7.         <data:DataGridTextColumn Header="Order ID" Binding="{Binding OrderID}" />   
  8.   
  9.         <!-- EmployeeID text column -->   
  10.         <data:DataGridTextColumn Header="Employee ID" Binding="{Binding EmployeeID}" />   
  11.   
  12.         <!-- OrderDate template column -->   
  13.         <data:DataGridTemplateColumn Header="Order Date" Width="150">   
  14.             <data:DataGridTemplateColumn.CellTemplate>   
  15.                 <DataTemplate>   
  16.                     <TextBlock Text="{Binding OrderDate}" />   
  17.                 </DataTemplate>   
  18.             </data:DataGridTemplateColumn.CellTemplate>   
  19.             <data:DataGridTemplateColumn.CellEditingTemplate>   
  20.                 <DataTemplate>   
  21.                     <basics:DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay}" />   
  22.                 </DataTemplate>   
  23.             </data:DataGridTemplateColumn.CellEditingTemplate>   
  24.         </data:DataGridTemplateColumn>   
  25.   
  26.         <!-- Freight template column -->   
  27.         <data:DataGridTemplateColumn Header="Freight" Width="150">   
  28.             <data:DataGridTemplateColumn.CellTemplate>   
  29.                 <DataTemplate>   
  30.                     <TextBlock Text="{Binding Freight}"></TextBlock>   
  31.                 </DataTemplate>   
  32.             </data:DataGridTemplateColumn.CellTemplate>   
  33.             <data:DataGridTemplateColumn.CellEditingTemplate>   
  34.                 <DataTemplate>   
  35.                     <StackPanel Orientation="Horizontal">   
  36.                         <TextBlock Text="{Binding Freight}" Width="50" />   
  37.                         <Slider Value="{Binding Freight, Mode=TwoWay}" Width="100"  
  38.                                 Minimum="0" Maximum="500" />   
  39.                     </StackPanel>   
  40.                 </DataTemplate>   
  41.             </data:DataGridTemplateColumn.CellEditingTemplate>   
  42.         </data:DataGridTemplateColumn>   
  43.     </data:DataGrid.Columns>   
  44. </data:DataGrid>  

같은 방식으로 DataTemplate 를 ListBox에도 사용할 수 있다. In a similar way, we can use DataTemplates for our ListBox. 우리가 가져올 데이터중에 비트맵이미지를 리턴해주는 PictureProperty가 있다고 한다면, 아래와 같은 방식으로 ListBox에 이미지를 보여줄 수도 있다.

  1. <ListBox x:Name="lstCustomer">   
  2.     <ListBox.ItemTemplate>   
  3.         <DataTemplate>   
  4.             <StackPanel Orientation="Vertical">   
  5.                 <TextBlock Text="{Binding NameProperty}"></TextBlock>   
  6.                 <Image Source="{Binding PictureProperty}"></Image>   
  7.             </StackPanel>   
  8.         </DataTemplate>   
  9.     </ListBox.ItemTemplate>   
  10. </ListBox>  

위 코드는 이렇게도 할수있다라는 샘플에 불과하니깐 우리 응용프로그램에는 적용시키지 말자. (우리가 가져오는 데이터에는 이미지가 없다.)



두번째 실행

다시 프로젝트를 실행해보자. OrderDate 칼럼을 더블클릭해서 DataPicker가 어떻게 나타나는지 보라. Freight 칼럼의 Slider를 조절하여 값을 조절해보고, 여러개의 컨트롤들이 어떻게 결합되어 하나의 칼럼에 나타나는지 유심히 보라. 알아채고 있었는지 모르겠지만, 지금 우리가 이렇게 칼럼을 수정해서 컨트롤을 집어넣은것은 모두 레이아웃코드에서 해결했다. *.cs 파일은 코딩도 하지 않았는데 말이다. 이게 바로 WPF 프레임웍의 장점이 아니겠는가!

사용자 삽입 이미지




DB에 변경데이터 적용하기

음, DB에 데이터를 다시 적용하는것은 이번 포스팅의 범위에서는 좀 벗어난다. 하지만 여러분이 WCF 서비스가 서버와 클라이언트사이에서 통신하는 것을 알고 있으면, 서비스쪽에 데이터를 저장하는 몇가지 함수를 만들고, 그 함수를 호출해서 사용한다면, 실버라이트에서도 쉽게 데이터 작성도 가능하도록 구현할 수 있다고 본다. 그리드에서 바인딩된 값이 변경될때 변경된값은 서버의 값이 아니라, 그리드에 바인딩된 DataContext 라는 것을 기억하자. 실제 DB에 변경을 하고 싶다면, ASP.NET 프로젝트의 WCF 서비스를 이용해서 변경된 데이터를 보내서 DB에 업데이트하도록 해야할 것이다. Ronnie Saurenmann의 동영상이(여기) 두개있는데, 그는 헬퍼 클래스를 이용해서 데이터를 마치 DataSet 처럼 다루고 있다. 변경된 데이터 관리라든지, 원본데이터라든지, 업데이트시에 변경된 데이터만 보내도록 한다든지 하는 방법을 보여주고 있다. 이 동영상을 꼭 한번 보기를 권장한다.



결론

이걸로 끝이다. 나는 이 포스팅을 통해 얼마나 간편하게 데이터관리 응용프로그램을 만들수있는지를 보여주고 싶었다. 배웠던것을 다시 한번 보자면, LINQ로 데이터엑세스 레이어를 만들었고, ASP.NET 프로젝트의 WCF 서비스를 통해 접근할 수 있도록 하였다. 그리고 실버라이트를 통해 클라이언트 측에서 데이터를 가져왔고, 이를 몇몇 컨트롤들을 사용해서 보여주었다.이 포스팅이 여러분들의 실버라이트 응용프로그램 제작의 동기가 될 수 있기를 바란다. 언제나 즐거운 실버라이트 프로그래밍을 하기를..



문서 이력

  • 2008년 08월 12일 - 기사 게시
  • 2008년 10월 19일 - Silverlight 2 RC1에 맞도록 업데이트
  • 2009년 01월 30일 - 한글로 번역되어 포스팅됨




라이센스

이 포스팅에 관련된 모든 파일과 소스코드는 The Code Project Open License (CPOL)를 따릅니다.



저자에 관해

Syed Mehroz AlamSyed Mehroz Alam has done his Bachelors in Computer and Information Systems Engineering in 2007. He loves logical challenges and has written certain logic games and mathematical applications in C/C#/VB in his early age which can be found at: http://www.geocities.com/smehrozalam/myprogs.html
He writes his blog at http://smehrozalam.wordpress.com
Any questions, comments or suggestions are always welcomed at smehrozalam at yahoo dot com.

번역

이 글의 원저자에게 양해를 구하고 번역하였습니다. 전문 번역가도 하니고 학습을 목적으로 번역을 했기때문에 서툰 표현이 많을 수 있습니다. 번역본을 퍼가실때는 출처를 남겨주시기 바랍니다.


Silverlight 2 projects can't be created with the Silverlight 3 Tools for Visual Studio 2008.  When Visual Studio 2010 is released, you will be able to build both Silverlight 2 and Silverlight 3 with the same IDE.  But for the immediate future, you will need Silverlight 3 Tools to build Silverlight 3 projects and Silverlight 2 Tools to build Silverlight 2 projects.

If you already have Silverlight 2 Tools installed, you can easily upgrade to Silverlight 3 Tools by running the Silverlight 3 installer.  To downgrade from Silverlight 3 to Silverlight 2 tools, you will need to uninstall "Microsoft Silverlight" and "Microsoft Silverlight Tools for Visual Studio 2008" from Add/Remove programs before re-installing Silverlight 2 Tools.

If you plan to switch frequently between Silverlight 2 and Silverlight 3, the utility below should make the process a little faster.

Setup

  1. Download Silverlight_Tools_Util.zip, and unzip
  2. Download the Silverlight 2 Tools and save Silverlight_Tools.exe in the Silverlight_Tools_Util folder
  3. Download the Silverlight 3 Tools and save Silverlight3_Tools.exe in the Silverlight_Tools_Util folder
  4. If you haven’t previously, run Silverlight_Tools.exe to install the Silverlight 2 Tools and SDK
  5. If you haven’t previously, run Silverlight3_Tools.exe to install the Silverlight 3 Tools and SDK
  6. Run Setup_SLTools_Util.bat to generate switching scripts

To install Silverlight 2 Tools for Visual Studio

  1. Close Visual Studio
  2. Run VS_3-to-2.bat

To install Silverlight 3 Tools for Visual Studio

  1. Close Visual Studio
  2. Run VS_2-to-3.bat

To install Silverlight 2 Tools for Visual Web Developer Express

  1. Close Visual Web Developer Express
  2. Run VWDx_3-to-2.bat

To install Silverlight 3 Tools for Visual Web Developer Express

  1. Close Visual Web Developer Express
  2. Run VWDx_2-to-3.bat

To downgrade a project from Silverlight 3 to Silverlight 2

  1. Open the .csproj or vbproj file in a text editor
  2. Change
    <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight\v3.0\Microsoft.Silverlight.CSharp.targets" Condition="" />
    To
    <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight\v2.0\Microsoft.Silverlight.CSharp.targets" Condition="" />

Tomorrow I'll leave for my annual trip to TechEd US again in Orlando. Looking forward to spend time with my buddies Ted and Fitz, and the rest of the gang in the US. Fitz recently joined Nintex and we'll have to catch up on that. From what I read on his blog, Nintex is not all about workflows. This week they'll RTM an interesting reporting layer for SharePoint. Read more here.

Anyway, I am presenting my session first thing Thursday morning. Title is 'Light Up Your SharePoint Web Site with Microsoft Silverlight and AJAX' and I'll highlight techniques for working with Web 2.0 technologies in the SharePoint space. Focus will be on hosting the Silverlight applications, your options to transfer data back and forth, consuming Web Services and WCF services, databinding and more.

I'll show also different techniques for the deployment of the Silverlight XAP file. A XAP file is basically a ZIP file containing the Silverlight solution components such as the compiled XAML and code-behind, an application manifest and possibly one or more assemblies delivering Silverlight controls. There are to me three possible places where you can make the XAP file available and the final choice depends more or less on your scope.

  • Drop the XAP in the ClientBin of your IIS Web Application. This is the most popular approach and also the technique we have used for the BluePrint samples. If you deploy in this location, it means that the Silverlight application can be picked up by any SharePoint code that runs in the site collections and sites hosted on the IIS Web Application. One of the disadvantages with this approach is that it is not directly possible to have the copying of the XAP in the ClientBin operation as part of the manifest of your SharePoint solution delivering the containers for the application (e.g. a Web Part). That is why for the BluePrint, we modified the SharePoint Solution Installer so that this extra step was taking care for.
  • Drop the XAP in the 12 Folder. If the Silverlight application needs to be scoped wider, you have for example a custom field type (which is by default a global deployed SharePoint solution), it is good to deploy in a sub folder of the 12\Template\Layouts or in the 12\Template\ControlTemplates folder. The 12\ISAPI is also a candidate. Deploying here means that you can include all of the deployment steps in your SharePoint Solution.
  • Drop the XAP in a Document Library. This is very often now my preferred choice of place to drop the XAP. You can create one central document library within your site collection (or if you want a more narrow scope, for your site) where to drop the XAP files. Just like with the previous 12 folder location, you can include this deployment also nicely in your Feature that for example makes available the Web Part hosting the Silverlight application.

Say for example that you create a Web Part with the Visual Studio Extensions for WSS 3.0. You can add the XAP file as part of the Feature folder in your Solution Explorer.

image

A Module and a File element in the element manifest file (lines 9-11) can take care of the provisioning of the XAP during the activation of the Feature in an existing document library (e.g. named XAPS).

   1: <Elements Id="c1f27c3d-0fab-46dc-b04d-a070b2713bbd" xmlns="http://schemas.microsoft.com/sharepoint/" >
   2:     
   3:   <Module Name="WebParts" List="113" Url="_catalogs/wp">
   4:       <File Path="HelloDevDays.webpart" Url="HelloDevDays.webpart" Type="GhostableInLibrary" >
   5:           <Property Name="Group" Value="DevDays Web Parts"></Property>
   6:       </File>
   7:   </Module>
   8:  
   9:     <Module Name="XAP" Url="XAPS">
  10:         <File Path="HelloDevDays.xap" Url="HelloDevDays.xap" Type="GhostableInLibrary" />
  11:     </Module>
  12:     
  13: </Elements>

In the Web Part where you create the Silverlight control, you then can point (line 6) to the XAP file using the following code:

   1: protected override void CreateChildControls()
   2: {
   3:     base.CreateChildControls();
   4:  
   5:     Silverlight ctrl = new Silverlight();
   6:     ctrl.Source = SPContext.Current.Site.RootWeb.Url + "/XAPS/HelloDevDays.xap";
   7:     ctrl.ID = "HelloDevDays";
   8:     ctrl.Width = new Unit(400);
   9:     ctrl.Height = new Unit(300);
  10:     ctrl.Version = "2.0";
  11:  
  12:     this.Controls.Add(ctrl);
  13:  
  14: }

As said, a nice and clean way to include all of the Silverlight files in your SharePoint Solution so that no more additional steps need to be taken after the deployment of the Web Part.

It is also very easy to upgrade the XAP files this way. But don't forget to clear your browser cache when you do this. Download the Internet Explorer Developer Toolbar to help you with that.



In IIS Management, click on the root node under Internet Information Services

Select the HTTP Headers tab and then click the MIME Types... button

 

Click the New button

Enter .xap for the Extensions and application/x-silverlight-app for the MIME type

Click OK, then OK, then Apply, then OK.

+ Recent posts