Blogs

The One With

July 2008 - Posts

  • Silverlight : More than one default template in generic.xaml?

         

    I was working on something recently that required more than one default template. In other words the default template to be used by a control was determined by another property.

    For example, suppose you have a Control called Shape

    public enum ShapeType {
    Red,
    Black,
    }

    public class Shape : Control {
    public Shape() {
    this.DefaultStyleKey = typeof(Shape);
    }

    public ShapeType Type {
    get { return (ShapeType)GetValue(TypeProperty); }
    set { SetValue(TypeProperty, value); }
    }
    }

    with a default style:

    <Style TargetType="local:Shape">
    <Setter Property="Template">
    <Setter.Value>
    <ControlTemplate TargetType="local:Shape">
    <Rectangle Fill="Red"></Rectangle>
    </ControlTemplate>
    </Setter.Value>
    </Setter>
    </Style>

    and you want the template to change based on the ShapeType. This is simple to do just by looking up the template from the resource dictionary.

    First thing to do is to refactor the generic.xaml a little bit so that different control templates are available as resource items.

    <ControlTemplate TargetType="local:Shape" x:Key="Shape:Black">
    <Rectangle Fill="Black"></Rectangle>
    </ControlTemplate>

    <ControlTemplate TargetType="local:Shape" x:Key="Shape:Red">
    <Rectangle Fill="Red"></Rectangle>
    </ControlTemplate>

    <Style TargetType="local:Shape">
    <Setter Property="Template" Value="{StaticResource Shape:Red}"></Setter>
    </Style>

    The initial value for the Template is the same as the default value for the ShapeType enum.

    Then, when the value of the Type changes, we will look up the control template:

    public static readonly DependencyProperty TypeProperty =
    DependencyProperty.Register("Type", typeof(ShapeType), typeof(Shape), new PropertyMetadata(new PropertyChangedCallback(delegate(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    Shape s = d as Shape;
    if (s != null) {
    s.OnShapeTypeChanged(true);
    }

    })));

     

    private string GetTemplateName(ShapeType type) {
    switch (type) {
    case ShapeType.Red:
    return "Shape:Red";
    case ShapeType.Black:
    return "Shape:Black";
    default:
    return null;
    }
    }
    protected void OnShapeTypeChanged(bool applyTemplate) {
    ResourceDictionary genericXaml = ResourceManager.GetResourceDictionary(null);
    if (genericXaml == null) {
    this.Template = null;
    return;
    }
    string templateName = GetTemplateName(this.Type);
    if (string.IsNullOrEmpty(templateName)) {
    this.Template = null;
    return;
    }
    this.Template = genericXaml[templateName] as ControlTemplate;
    if (applyTemplate) {
    this.ApplyTemplate();
    }
    }

    An additional helper class ResourceManager:

    internal static class ResourceManager {
    private static Dictionary<string, ResourceDictionary> _resourceDictionaryCache =
    new Dictionary<string, ResourceDictionary>();

    /// <summary>
    /// GetResourceDictionary
    /// </summary>
    public static ResourceDictionary GetResourceDictionary(string name) {
    var resourceName = name;
    if (string.IsNullOrEmpty(resourceName)) {
    resourceName = "generic.xaml";
    }
    ResourceDictionary retVal = null;
    if (_resourceDictionaryCache.TryGetValue(resourceName, out retVal)) {
    return retVal;
    }
    System.Reflection.Assembly assembly = typeof(ResourceManager).Assembly;
    string baseName = string.Format("{0}.g",
    assembly.FullName.Split(new char[] { ',' })[0]);
    string fullName = string.Format("{0}.resources", baseName);
    foreach (string s in assembly.GetManifestResourceNames()) {
    if (s == fullName) {
    System.Resources.ResourceManager manager =
    new System.Resources.ResourceManager(baseName, assembly);
    using (System.IO.Stream stream = manager.GetStream(resourceName)) {
    using (System.IO.StreamReader reader = new System.IO.StreamReader(stream)) {
    retVal = System.Windows.Markup.XamlReader.Load(reader.ReadToEnd()) as ResourceDictionary;
    if (retVal != null) {
    _resourceDictionaryCache.Add(resourceName, retVal);
    return retVal;
    }
    }
    }
    break;
    }
    }
    return null;
    }
    }

    And that's it.

     

    Cheers,

    Azret

  • Silverlight Data Grid: Master Detail View by using the RowPreview template.

         

    The cool thing about the Preview area of the data row, is that you can place anything there. For example you can place a simple text or an image and data bind it to the current DataContext, which is in fact your data row object. So if you have another AgDataGrid inside your preview template, you can bind it's DataSource to the current DataContext.

    So if, our master grid is bound to a collection of customer objects and each customer has a list of orders.
     
    public class Order {
        public int OrderId { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
    }
    
    public class Customer {
        public string Name { get; set; }
        public string Country { get; set; }
        private List<Order> _orders = new List<Order>();
        public List<Order> Orders { get { return this._orders; } }
    }
     
    List<Customer> GetCustomers() {
        Customer c;
        List<Customer> list = new List<Customer>();
    
        c = new Customer() { Name = "Joe", Country = "USA" };
        c.Orders.Add(new Order() { OrderId = 0, Description = "Sterio", Price=2000 });
        c.Orders.Add(new Order() { OrderId = 1, Description = "XP Radio", Price=1000 });
        c.Orders.Add(new Order() { OrderId = 2, Description = "Cell Phone", Price=200 });
        list.Add(c);
    
        c = new Customer() { Name = "Bill", Country = "USA" };
        c.Orders.Add(new Order() { OrderId = 3, Description = "Sterio", Price=2000 });
        c.Orders.Add(new Order() { OrderId = 4, Description = "Head Set", Price=100 });
        c.Orders.Add(new Order() { OrderId = 5, Description = "Keyboard", Price=100 });
        list.Add(c);
        return list;
    }
     
    then the Customer.Orders could be bound to a DataSource of the grid that is inside the preview row like so:
     
    <DevExpress:AgDataGrid  
        x:Name="dataGrid" PreviewVisibility="ForAllRows" 
        ColumnsAutoWidth="True" ShowGroupPanel="Visible">
                
        <DevExpress:AgDataGrid.Columns>
                    <DevExpress:AgDataGridColumn FieldName="Name"></DevExpress:AgDataGridColumn>
                    <DevExpress:AgDataGridColumn FieldName="Country"></DevExpress:AgDataGridColumn>
        </DevExpress:AgDataGrid.Columns>
        <DevExpress:AgDataGrid.PreviewTemplate>
                    <DataTemplate>
                        <Grid  Height="180">
                            <DevExpress:AgDataGrid AutoGenerateColumns="True"
                                    DataSource="{Binding Orders}" ColumnsAutoWidth="True" Margin="5">
                            </DevExpress:AgDataGrid>
                        </Grid>
                    </DataTemplate>
        </DevExpress:AgDataGrid.PreviewTemplate>
     </DevExpress:AgDataGrid>
     
     
    Cheers,
    Azret
     
  • Silverlight DataGrid: Unbound Mode

         

    Antoine Habert has written a very cool blog post on how to simulate the Unbound mode with AgDataGrid with a help of a Dictionary.

    http://devinfra-us.blogspot.com/2008/07/agdatagrid-and-silverlight-datagrid-how.html

     

    --

    Azret

  • Silverlight Data Grid - Creating an IM/Chat Server and Client using Sockets

         

    One of the things I love about Silverlight 2 Beta 2+, is that cross domain networking is now possible and all you have to do is provide a policy file on your target server that will dictate the security permissions. For your HTTP traffic it is just a matter of dropping a clientaccesspolicy.xml file at the root of your domain and for raw sockets you provide this file via a special port 943. Having support for cross-domain networking means that real time clients are now easy to develop. In this article we will use Sockets to build a simple chat application.

    The Server

    Before any Socket connection could be made by a Silverlight client, the runtime will try to connect to a special port 943 to get the cross domain policy information. It will send a <policy-file-request/> string for the request so we will need to reply back with a valid cross-domain policy xml.

    In our case we will send back a simple "allow all" configuration.

    <?xml version="1.0" encoding ="utf-8"?>
    <access-policy>
        <cross-domain-access>
            <policy>
                <allow-from>
                    <domain uri="*" />
                </allow-from>
                <grant-to>
                    <socket-resource port="4510" protocol="tcp" />
                </grant-to>
            </policy>
        </cross-domain-access>
    </access-policy>
     
    Policy Server implementation:
     
    public class PolicyServer : SocketServer {
        public static readonly byte[] PolicyBuffer = System.IO.File.ReadAllBytes("clientaccesspolicy.xml");
    
        public PolicyServer()
            : base(943) {
        }
    
        protected override void OnAccept(Socket client) {
            PostReceive(client);
        }
    
        protected override void OnReceive(Socket client, byte[] receivedBytes, int count) {
            string request = Encoding.UTF8.GetString(receivedBytes);
            if (!string.IsNullOrEmpty(request) && request.StartsWith("<policy-file-request/>")) {
                client.Send(PolicyBuffer);
            }
            client.Close();
        }
    }

    At a minimum, there are 3 commands that our server will need to handle.

    • Login
    • RefreshUsers
    • Message

    I will use XML to send the commands back and forth since it is easy to serialize them using the XML Serialization facilities provided by the .NET framework.

    public enum Operation {
        Unknown,
        Login,
        Message,
        RefreshUsers,
    }
    
    [XmlRoot("Packet")]
    public class Packet {
        public Packet() {
            this.Children = new List<User>();
        }
        public Guid UserId { get; set; }
        public string UserName { get; set; }
        public Operation Operation { get; set; }
        public string Payload { get; set; }
        public List<User> Children { get; set; }
    }
    • UserId - A unique ID for a specific user.
    • Children - List of User objects for the RefreshUsers command.
    • Payload - Text for the Message command.

    A helper class to convert our Packet object to and from XML:

    public static class Protocol {
    
        public static byte[] GetPacket(Packet packet) {
            XmlSerializer serializer = new XmlSerializer(typeof(Packet));
            using (MemoryStream stream = new MemoryStream()) {
                serializer.Serialize(stream, packet);
                return stream.GetBuffer();
            }
        }
    
        public static Packet GetPacket(byte[] buffer, int count) {
            XmlSerializer serializer = new XmlSerializer(typeof(Packet));
            using (MemoryStream stream = new MemoryStream()) {
                stream.Write(buffer, 0, count);
                stream.Position = 0;
                return serializer.Deserialize(stream) as Packet;
            }
        }
    }

    Handling the requests:

    protected override void OnReceive(Socket client, byte[] receivedBytes, int count) {
        try {
            DXTalk.Packet packet = DXTalk.Protocol.GetPacket(receivedBytes, count);
            switch (packet.Operation) {
                case Operation.Login:
                    UpdateUserInfo(client, packet);
                    BroadcaseUserList();
                    break;
                case Operation.RefreshUsers:
                    BroadcaseUserList();
                    break;
                case Operation.Message:
                    ForwardMessage(packet);
                    break;
            }
        } finally {
            PostReceive(client);
        }
    }
    

    Now we just start up the 2 listening sockets: one for our policy xml and another for handling the protocol requests and we are done with the server side. There is one restriction on the port usage however, it must in 4502-4534 range. We will use 4510 for our talk server.

     

    Establishing the connection

    The Socket API is a little bit different in Silverlight then in the full .NET framework. It only supports asynchronous calls and all the calls use a special SocketAsyncEventArgs class for completion events and state.

    Connection to our chat server:

    void Login(string userName) {
        if (_socket == null) {
            _socket = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);
    
            SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
            sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnected);
            sendArgs.RemoteEndPoint = new DnsEndPoint("localhost", 4510);
    
            _loginId = Guid.NewGuid();
            _loginName = userName;
            _socket.ConnectAsync(sendArgs);
        }
    }
    
    void OnConnected(object sender, SocketAsyncEventArgs e) {
        SocketError error = e.SocketError;
        if (error == SocketError.Success) {
            _socket.BeginReceive(new EventHandler<SocketAsyncEventArgs>(OnReceiveComplete));
            _socket.BeginSend(new Packet() {
                UserId = this._loginId,
                UserName = this._loginName,
                Operation = Operation.Login,
            }, null);
        }
    }

    BeginReceive and BeginSend?! Don' worry, those are just extension methods on the Socket class that call ReceiveAsync and SendAsync :)

    public static class SocketExtentions {
        
        #if SILVERLIGHT
    
        public static void BeginSend(this Socket socket, 
            Packet packet,
            EventHandler<SocketAsyncEventArgs> completionEvent) {            
            SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
            sendArgs.Completed += completionEvent;
            byte[] buffer = Protocol.GetPacket(packet);
            sendArgs.SetBuffer(buffer, 0, buffer.Length);
            socket.SendAsync(sendArgs);
        }
        
        public static void BeginReceive(this Socket socket,
            EventHandler<SocketAsyncEventArgs> completionEvent) {
            SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
            byte[] buffer = new byte[1024];
            sendArgs.SetBuffer(buffer, 0, buffer.Length);
            sendArgs.Completed += completionEvent;
            socket.ReceiveAsync(sendArgs);
        }
        
        #endif
    
    }

    When we receive the reply from the server, OnReceiveComplete event is invoked:

    void OnReceiveComplete(object sender, SocketAsyncEventArgs e) {
        SocketError error = e.SocketError;
        if (error == SocketError.Success) {
            try {
                Packet packet = Protocol.GetPacket(e.Buffer, e.BytesTransferred);
                if (packet.Operation == Operation.RefreshUsers) {
                    this.Dispatcher.BeginInvoke(delegate() {
                        Users.DataSource = packet.Children;
                        Users.ExpandAll();
                    });
                } else if (packet.Operation == Operation.Message) {
                    this.Dispatcher.BeginInvoke(delegate() {
                        ShowChatWindow(packet.Children[0], packet.Payload);
                    });
                }
            } catch {
            }
        }
        _socket.BeginReceive(new EventHandler<SocketAsyncEventArgs>(OnReceiveComplete));
    }
    

    Two things to note here. 1: "Users" is an AgDataGrid control:

    <TalkControl:AgDataGrid Grid.Row="1" x:Name="Users"
                           ShowGroupPanel="Visible"
                           ColumnsAutoWidth="True"
                           AllowEditing="False">
        <TalkControl:AgDataGrid.Columns>
            <DevExpress:AgDataGridTextColumn HeaderContent="User Name" FieldName="UserName"/>
            <DevExpress:AgDataGridTextColumn HeaderContent="User ID" FieldName="UserId"/>
        </TalkControl:AgDataGrid.Columns>
    </TalkControl:AgDataGrid>        

    And 2, we must synchronize access to the main thread via the Dispatcher object.

     

    Preparing the UI

    Once the client is connected, a conversation could be started either by receiving a message or by double clicking the a record in the data grid. In both cases, the same ShowChatWindow function is called:

    private void ShowChatWindow(User peer, string message) {
        ChatWindow window = ChatWindow.FindWindow(peer.UserId);
        if (window == null) {
            Popup popup = new Popup();
            window = new ChatWindow(popup,
                new User() { UserId = _loginId, UserName = _loginName },
                peer, _socket);
            window.Title.Text = peer.UserName;
            popup.VerticalOffset = 100;
            popup.HorizontalOffset = 100;
            popup.Child = window;
            popup.IsOpen = true;
        }
        if (!string.IsNullOrEmpty(message)) {
            window.AddLine(message, Colors.Red, peer);
        }
        window.parent.IsOpen = true;
        window.Focus();
    }

    There is no support for double-click in Silverlight so we will have to fake it using a special MouseHelper class that I copied from the AgDataGrid source code.

    public delegate void DblClickEvent(object sender, MouseEventArgs e);
    
    public class AgDataGrid : DevExpress.Windows.Controls.AgDataGrid {
        public void Refresh() {
            base.DataController.DoRefresh();
        }
    
        private static class MouseHelper {
            static int Timeout = 500;
            static bool clicked = false;
            static Point position;
            public static bool IsDoubleClick(MouseButtonEventArgs e) {
                if (clicked) {
                    clicked = false;
                    return position.Equals(e.GetPosition(null));
                }
                clicked = true;
                position = e.GetPosition(null);
                ParameterizedThreadStart threadStart = new ParameterizedThreadStart(ResetThread);
                Thread thread = new Thread(threadStart);
                thread.Start();
                return false;
            }
            private static void ResetThread(object state) {
                Thread.Sleep(Timeout);
                clicked = false;
            }
        }
    
        public event DblClickEvent DblClick;
    
        public override void OnApplyTemplate() {
            base.OnApplyTemplate();
            this.Surface.MouseLeftButtonUp += new MouseButtonEventHandler(Surface_MouseLeftButtonUp);
        }
    
        void Surface_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
            if (MouseHelper.IsDoubleClick(e)) {
                if (DblClick != null) {
                    DblClick(sender, e);
                }
                e.Handled = true;
            }
        }
    }

    And there no build-in windows or dialogs in Silverlight 2. But there is a System.Windows.Controls.Primitives.Popup container and it's all we need to create a conversation window. A ChatWindow is a simple UserControl and in the ChatWindow.xaml I have one special element TitleBar.

    <Grid Background="Black" Grid.Row="0" x:Name="TitleBar">
        <Border BorderBrush="White" BorderThickness="0.3"></Border>
        <TextBlock  IsHitTestVisible="False" x:Name="Title" Text="Title" VerticalAlignment="Center" HorizontalAlignment="Left"
                   FontFamily="Tahoma" Foreground="White" FontSize="14" 
                    FontWeight="Bold"
                    Margin="8,0,0,0"></TextBlock>
        <Button Width="32" Margin="4" HorizontalAlignment="Right" Content="X"
            Click="CloseButtonClick"></Button>
    </Grid>

    It is used to display the NC (non-client) area of the window with the close button and it is also used to control the dragging of the window. We must have cool UI :). Implementing the window dragging is simple:

    public ChatWindow()
        : base() {
        InitializeComponent();
        this.MouseLeftButtonDown += new MouseButtonEventHandler(ChatWindow_MouseLeftButtonDown);
        this.MouseLeftButtonUp += new MouseButtonEventHandler(ChatWindow_MouseLeftButtonUp);
        this.MouseMove += new MouseEventHandler(ChatWindow_MouseMove);
    }
    
    bool _hasNCHitTest = false;
    Point _initalNCHitTest = new Point();
    
    void ChatWindow_MouseMove(object sender, MouseEventArgs e) {
        if (e.Handled == true)
            return; 
        if (_hasNCHitTest && parent != null) {
            Point hitTest;
            hitTest = e.GetPosition(Title);
    
            Point delta = new Point(
                hitTest.X - _initalNCHitTest.X,
                hitTest.Y - _initalNCHitTest.Y);
    
            this.parent.HorizontalOffset += delta.X;
            this.parent.VerticalOffset += delta.Y;
            
            e.Handled = true;
        }
    }
    
    void ChatWindow_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
        if (e.Handled == true)
            return; 
        if (_hasNCHitTest) {
            _hasNCHitTest = false;
            TitleBar.ReleaseMouseCapture();
            e.Handled = true;
        }
    }
    
    void ChatWindow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
        if (e.Handled == true)
            return; 
        if (e.Source == TitleBar && parent != null) {
            _hasNCHitTest = true;                
            _initalNCHitTest = e.GetPosition(TitleBar);
            TitleBar.CaptureMouse();
            e.Handled = true;
        }
    }
    

    For conversation area, I use the AgDataGrid as well with a custom preview template. Where the grid is called ChatBox and is bound to a list of Message objects.

    <TalkConrol:AgDataGrid Grid.Row="1" x:Name="ChatBox" AllowEditing="False" ShowColumnHeaders="Collapsed" ShowHorizontalLines="False" ShowVerticalLines="False" PreviewVisibility="ForAllRows" ColumnsAutoWidth="True"> <TalkConrol:AgDataGrid.DataRowTemplate> <ControlTemplate TargetType="DevExpress:AgDataGridRow"> <Grid Name="RootElement"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <DevExpressInternal:AgLineStackPanel x:Name="CellsPresenterElement" Opacity="0" Height="1" /> <ContentControl x:Name="PreviewPresenterElement" Height="0"/> </Grid> </ControlTemplate> </TalkConrol:AgDataGrid.DataRowTemplate> <TalkConrol:AgDataGrid.PreviewTemplate> <DataTemplate> <StackPanel Orientation="Vertical" HorizontalAlignment="Left" Margin="6,0,0,0"> <TextBlock Text="{Binding Timestamp}" TextWrapping="NoWrap" Margin="2,0,0,0" Width="300" Foreground="Gray" FontFamily="lucida grande,tahoma,verdana,arial,sans-serif" FontSize="11"></TextBlock> <TextBlock Text="{Binding Line}" TextWrapping="Wrap" Margin="16,1,0,0" Width="300" Foreground="{Binding Color}" FontFamily="lucida grande,tahoma,verdana,arial,sans-serif" FontSize="11"></TextBlock> </StackPanel> </DataTemplate> </TalkConrol:AgDataGrid.PreviewTemplate> </TalkConrol:AgDataGrid>

    public class Message {
        public string Timestamp { get; set; }
        public string Line { get; set; }
        public Brush Color { get; set; }
    }
    
    internal void AddLine(string message, Color color, User peer) {
        var list = this.ChatBox.DataSource as List<Message>;
        list.Add(new Message() {
            Line = string.Format("{0}: {1}", peer.UserName, message),
            Timestamp = DateTime.Now.ToString(), Color = new SolidColorBrush(color)
        });
        ChatBox.Refresh();
    }

    One last thing, sending the actual message and we are done:

    private void Send_Click(object sender, RoutedEventArgs e) {
        if (!string.IsNullOrEmpty(MessageBox.Text)) {
            Packet packet = new Packet() {
                UserId = user.UserId,
                UserName = user.UserName,
                Operation = Operation.Message,
                Payload = MessageBox.Text,
                Children = new List<User>() { new User() { UserId = peer.UserId, UserName = peer.UserName } },
            };
            AddLine(MessageBox.Text, Colors.Black, this.user);
            socket.BeginSend(packet, null);
        }
    }

     

    image 

    Download Source Code

     

    Happy chatting :)

    Azret

  • Silverlight DataGrid - How to perform custom summary calculations within the grid control

         

    There are 5 build-in aggregate functions that our Silverlight DataGrid supports (Max, Min, Avg, Sum and Count), and an ability to define custom ones.

     

     

    Using build-in Summary Items

    <DevExpress:AgDataGrid>

       <DevExpress:AgDataGrid.Totals>

           <DevExpress:AgDataGridSummaryItem FieldName="Points" SummaryType="Average"/>

           <DevExpress:AgDataGridSummaryItem FieldName="Points" SummaryType="Max"/>

           <DevExpress:AgDataGridSummaryItem FieldName="Points" SummaryType="Min"/>

           <DevExpress:AgDataGridSummaryItem FieldName="Points" SummaryType="Count"/>

           <DevExpress:AgDataGridSummaryItem FieldName="Points" SummaryType="Sum"/>  

       </DevExpress:AgDataGrid.Totals>

    </DevExpress>

     

    Calculating Custom Summary

    To calculate custom summary, we need to add an AgDataGridSummaryItem with SummaryType of "Custom" and use the AgDataGrid.CustomSummary event.

    <DevExpress:AgDataGrid CustomSummary="myGrid_CustomSummary">

       <DevExpress:AgDataGrid.Totals>

             <DevExpress:AgDataGridSummaryItem FieldName="Points" SummaryType="Custom"

                                              Title="Sum Of Even Rows"/>

       </DevExpress:AgDataGrid.Totals>

    </DevExpress>

    private void myGrid_CustomSummary(object sender, DevExpress.Windows.Data.CustomSummaryEventArgs e) {              

                      if (e.SummaryProcess == CustomSummaryProcess.Start) {

                            int rowHandle = 0;                       

                            int sumOfEvenRows = 0;

                            int i = 1;

                            while (myGrid.IsValidRowHandle(rowHandle)) {

                                  Item item = (Item)myGrid.GetDataRow(rowHandle);

                                  if ((i % 2) == 0){

                                sumOfEvenRows += item.Points;

                                  }

                                  rowHandle++;

                                  i++;

                            }

                            e.TotalValue = sumOfEvenRows;

                            e.TotalValueReady = true;

                   }

    }

    public class MyRowItem {

          public int Points { get; set; }

    }

    All we are doing here is going through every row in our data source and summing up the Points for even rows.

     

    Previous: Silverlight DataGrid - Where are my Facebook friends?

     

    Cheers,

    Azret

  • Silverlight Control Builder Contest

         

    If you have not entered the Silverlight Control Builder Contest yet you should hurry up, only 31 Days remaining, and Developer Express will be offering some cool prizes to the winners:

     

    1st Place

    And a $500 gift certificate to NewEgg.com

    2nd Place

    And a $250 gift certificate to NewEgg.com

    3rd Place

    Code Rush and Refactor Pro!

    And a $150 gift certificate to NewEgg.com

    Good luck,

    Azret

  • Silverlight DataGrid - Where are my Facebook friends?

         

    So I was writing a blog on how to bind data to our Silverlight DataGrid from a WebService call and got bored. AgDataGrid.DataSource supports any collection that implements an IList or IEnumerable, and since all the collection objects that are generated by the Visual Studio's Web Service wizards already implement those, there was nothing really interesting to write about. I was searching for some cool WebService to use and remembered that there is this thing called Facebook (a tiny social networking website) and there are these friends there and how cool would it be if I can group them by say a City?

    Prerequisites

    Silverlight 2 Beta 2 Tools for Visual Studio 2008

    AgDataGrid Beta 1

    Facebook Developer Toolkit

    Facebook Account

    Join the Developer Express on Facebook  :)

    If you are just starting out with AgDataGrid and Silverlight development, or need a quick refresher, watch these quick-start videos:

     

    The Setup

    First things first. You will need a special Facebook Developer Application added to your profile. This is where you create applications to be run inside Facebook. Once you add that, click on Setup New Application and go through the setup process. You will need to set a couple of options:

    a) Set the Callback URL to http://localhost:9999. This is the URL that the Facebook will redirect to.

    b) Select "Use iframe"

    c) Select "Yes" for Can your application be added on Facebook?

    Once you finish the setup part there will be two unique keys assigned to your app. The Developer Key and the Secret Key. We will need those when making a Facebook API calls.

    Next, create a new Silverlight application and choose an option to generate a Web Application project as well.

     

     

    Creating a proxy Web Service

    The anatomy of the Facebook platform and API is beyond the scope for this article. Instead, we will use a Facebook Developer Toolkit developed and maintained by the community. To be able to make a Facebook API call, a user must be logged in to his or her account. Once the user logs in and navigates to a specific app, Facebook will setup an iframe and pull the content from the Callback URL. It will also pass in an authToken parameter, that will be used to get the user id, session key etc... We will need those to make API calls. In your Web Project, add a reference to the Facebook.dll assembly (or a Facebook.csproj), the one that you downloaded from the toolkit.

    A typical page load scenario would look something like this: Redirect to the facebook login screen if this is the first time. Once redirected back, request a facebook_session_key and a facebook_userid using the authToken that is passed in and store them inside the Session for later use.

    protected void Page_Load(object sender, EventArgs e) {

          string sessionKey = Session["facebook_session_key"] as string;

          string userId = Session["facebook_userId"] as string;

          string authToken = Request.QueryString["auth_token"];

     

          if (!string.IsNullOrEmpty(sessionKey)) {

                // already have a session

                FacebookAPI.SessionKey = sessionKey;

                FacebookAPI.UserId = userId;

          } else if (!string.IsNullOrEmpty(authToken)) {

                // on redirect

                FacebookAPI.CreateSession(authToken);

                Session["facebook_session_key"] = FacebookAPI.SessionKey;

                Session["facebook_userId"] = FacebookAPI.UserId;

                Session["facebook_session_expires"] = FacebookAPI.SessionExpires;

                Session["facebook_api"] = FacebookAPI;

          } else {

                Response.Redirect(@"http://www.facebook.com/login.php?api_key=" +

                      FacebookAPI.ApplicationKey + @"&v=1.0");

          }

    }

     

    Next, we will add a Silverlight-enabled WCF Service to the web project and create a method to return a user profile filled with friends.

    public class UserInfo {      

    public string Name { get; set; }

    public string Status { get; set; }

    public string UserId { get; set; }

    public byte [ ] Picture { get; set; }

    // etc..

    }

    public class UserProfile : UserInfo {

    public List<UserInfo> Friends { get; set; }

    }

    [OperationContract]

    public UserProfile GetUserProfile(string userId) {

          Facebook.API.FacebookAPI facebookAPI = HttpContext.Current.Session["facebook_api"]

                as Facebook.API.FacebookAPI;

          ...

          var list = facebookAPI.GetUserInfo(userId);

          var profile = list[0];

          var proxy_profile = new UserProfile(profile) {

                Friends = new List<UserInfo>(),

                Picture = ImageHelper.GetBytesFromWeb(profile.PictureBigUrl),

          }; 

          var friends = facebookAPI.GetFriends();

          foreach (User friend in friends) {

                proxy_profile.Friends.Add(new UserInfo(friend) {

                      Picture = ImageHelper.GetBytesFromWeb(friend.PictureSquareUrl),

                });

          } 

          return proxy_profile;

    }

    One last thing on the server side. Make sure to drop a Silverlight object on your Default.aspx page and link it to your .xap file. Also in your project Web options, set the port to 9999.

     

    Client Side Binding

     

    Add a reference to DevExpress.AgDataGrid.v8.2 in your Silverlight app, and drop the AgDataGrid on the page.

     

    xmlns:DevExpress="clr-namespace:DevExpress.Windows.Controls;assembly=DevExpress.AgDataGrid.v8.2"

    ..

    <DevExpress:AgDataGrid ShowGroupPanel="Visible" AllowEditing="False"

                               x:Name="FriendList"

                               PreviewVisibility="ForAllRows"

                               ColumnsAutoWidth="True">

          <DevExpress:AgDataGrid.Columns>

                <DevExpress:AgDataGridTextColumn FieldName="Name"/>

                <DevExpress:AgDataGridTextColumn FieldName="City"/>

                <DevExpress:AgDataGridTextColumn FieldName="State"/>

                <DevExpress:AgDataGridTextColumn FieldName="Country"/>

                <DevExpress:AgDataGridImageColumn FieldName="Picture"/>

          </DevExpress:AgDataGrid.Columns>

    </DevExpress:AgDataGrid>

     

    Notice I use the AgDataGridImageColumn column type to bind to the Picture field. Internally, it will use an AgDataGridImageColumnContentConverter (an IValueConverter) to correctly convert our byte[].

    AgDataGridImageColumnContentConverter comes in handy when binding to an Image control as well.

     

    For example:

    <UserControl.Resources>

          <DevExpress:AgDataGridImageColumnContentConverter x:Key="BytesToImageConverter"/>

    </UserControl.Resources>

    <Image Source="{Binding Picture, Converter={StaticResource BytesToImageConverter}}"/ >

     

    After adding a reference to the above service, we can bind the result to our UI.

     

    uses FacebookServiceReference;

     

    public Page() {

          InitializeComponent();

          ShowUserProfile(null);

    } 

    void ShowUserProfile(string userId) {

          FacebookServiceClient client = new FacebookServiceClient();

          client.GetUserProfileCompleted += new EventHandler<GetUserProfileCompletedEventArgs>(FacebookServiceClient_GetUserProfileCompleted);

          client.GetUserProfileAsync(userId);

    }

    void FacebookServiceClient_GetUserProfileCompleted(object sender, GetUserProfileCompletedEventArgs e) {

          if (e.Error == null) {

                this.FriendList.DataSource = e.Result.Friends;

          }

    }

     

    Tweaking the UI - Changing how a data row looks

    To change how a data row looks inside AgDataGrid we will reuse the row preview area to define a custom layout and hide the default data row.

    Hide the default data row:

    <DevExpress:AgDataGrid.DataRowTemplate>

          <ControlTemplate TargetType="DevExpress:AgDataGridRow">

                <Grid Name="RootElement">

                      <Grid.RowDefinitions>

                            <RowDefinition Height="Auto"/>

                      </Grid.RowDefinitions>

                      <Grid.ColumnDefinitions>

                            <ColumnDefinition Width="Auto" />

                      </Grid.ColumnDefinitions>

                      <DevExpressInternal:AgLineStackPanel x:Name="CellsPresenterElement" Opacity="0" Height="1" />

                      <ContentControl x:Name="PreviewPresenterElement" Height="0"/>

                </Grid>

          </ControlTemplate>

    </DevExpress:AgDataGrid.DataRowTemplate>

     

    Define a custom row preview:

    <DevExpress:AgDataGrid

                ShowGroupPanel="Visible" AllowEditing="False"

                x:Name="FriendList"    

                PreviewVisibility="ForAllRows"

                ColumnsAutoWidth="True">

     

    <DevExpress:AgDataGrid.PreviewTemplate>

          <DataTemplate>

                <Grid Margin="8">

                      <Grid.ColumnDefinitions>

                            <ColumnDefinition Width="50"></ColumnDefinition>

                            <ColumnDefinition Width="*"></ColumnDefinition>

                            <ColumnDefinition Width="50"></ColumnDefinition>

                      </Grid.ColumnDefinitions>

                      <Image Width="50" Height="50" Stretch="Fill" Grid.Column="0"

                            Source="{Binding Picture, Converter={StaticResource BytesToImageConverter}}"

                            VerticalAlignment="Top"

                            HorizontalAlignment="Left">

                      </Image>

                      <StackPanel Orientation="Vertical" Grid.Column="1" HorizontalAlignment="Left" Margin="8,0,0,0">

                            <TextBlock Text="{Binding Name}" FontFamily="lucida grande,tahoma,verdana,arial,sans-serif"

                                              FontSize="14" Foreground="#3B5998" Cursor="Hand"

                                              FontWeight="Bold" Tag="{Binding UserId}"

                                              MouseLeftButtonUp="UserBlock_MouseLeftButtonUp"></TextBlock>

                            <TextBlock Text="{Binding Status}" TextWrapping="Wrap"

                                              Margin="2,0,0,0"

                                              Width="300"

                                              FontFamily="lucida grande,tahoma,verdana,arial,sans-serif"

                                              FontSize="11"></TextBlock>

                      </StackPanel>

                </Grid>

          </DataTemplate>

    </DevExpress:AgDataGrid.PreviewTemplate>

     

    The end result:

     

    Download Source Code

     

    Previous: Silverlight DataGrid - Creating custom column types for the grid control

    Next: Silverlight DataGrid - How to perform custom summary calculations within the grid control

     

    Cheers

    Azret

  • Silverlight DataGrid - Los Angeles .Net Developers Group

         

    I will be showing off the AgDataGrid in action at the Los Angeles .NET Developers Group on Monday, July 07. Stop by if you are in the area and have time. (There will be prizes :))

    Here are the Directions.

     

    Cheers

    Azret

  • Report Parameters - (Whats new in v8.2)

         

    In my previous post about what's new in XtraReports for v2008.2 I teased about this new feature. And some of you have guessed it right. Report parameters!.

    XtraReport now has a Parameters collection where you can define your params.

    Each report parameter can be used anywhere in the report. In filtering expressions, in the formula builder dialog, etc.

    For example:

    "[Country] = [Parameters.parameter1]"

    Now all we need is some UI where an end user can enter the parameter value. And for the default print preview dialogs, that UI is generated automatically.

     

    Cheers,

    Azret

  • iCalendar Support - (What's new in v8.2)

         

    I know some of you have been waiting for this. In v2008.2, both the XtraScheduler and the APSxScheduler suites will support the iCalendar (or iCal) RFC 2445 standard for data exchange.

    This means data exchange between the most popular calendars out there. Apple iCal, Lotus Notes, Google Calendar, Novell GroupWise, and Windows Calendar just to name a few.

    XtraScheduler -> export to iCal

    Google Calendar -> import from iCal

     

    Cheers

    Azret

  • Reports and Calculated Fields - (What's new in v8.2)

         

    There is a number of new features added to the XtraReports suite for v8.2. A couple of them are very exciting. One is calculated fields. Yeah baby :)

    To add a calculated field, simply right click on your report data source in the designer and select Add Calculated Field.

    Once the field has been added you can use the expression editor to specify your formula. Right click on the newly created field and choose Edit Expression.

    Expressions support a number of functions and operators (plus, minus etc... even in, like and between). When a calculated field has been set up, you can use it as an ordinary data field anywhere across your reports.

    Its values can be simply displayed in report controls, or can take part in data grouping, sorting or filtering.

     

    Cheers

    Azret

    P.S:  Who can spot another big new feature ?

  • PivotGrid (Pivot Table) and inplace editing - (What's new in v8.2)

         

    XtraPivotGrid is a life saver if you need to provide in-depth analyses and data mining features to your end users. If you are not familiar with this control then please read about it here http://www.devexpress.com/Products/NET/WinForms/XtraPivotGrid/ and here http://www.devexpress.com/Products/NET/WinForms/XtraPivotGrid/QuickStart.xml

    From drilling down through your OLAP data source, live filtering, charts integration, and countless customization features, this control has it all. Those who use it can't get enough of it, and those who learn about it try to find a way to include it in their applications.

    One piece was missing however. It's the support for data editing. First of all, what does it really mean to edit a summary value? Sure, one can drill down to an individual cell and modify it there, but what if the value is an aggregate of two or more? What should be done then? Well, it all depends on your data and your aggregates. Creating a universal approach that will cover all possible data editing scenarios seemed impractical, and that is not what our customers wanted. "Just let us modify cell values and we'll take care of updating the data source," we were told.

    Unleashing the power of XtraEditors library

    It is only natural to reuse the editors library that is used in our other product suites. (XtraGrid, XtraBars etc...). And editor embedding in the pivot grid is as easy as choosing from a repository of available editors.

    But first things first. Let's create a pivot grid and bind it to some mock data.

    public class Fact {

                public string Name {

                      get;

                      set;

                }

                public string Item {

                      get;

                      set;

                }

                public decimal Amount {

                      get;

                      set;

                }

          }

     

    public class Facts : List<Fact> {

                public static Facts GetMockData() {

                      return new Facts() {

                            new Fact() { Name = "Joe Smith", Item = "Apples", Amount = 1000 },

                            new Fact() { Name = "Joe Smith", Item = "Apples", Amount = 2000 },

                            new Fact() { Name = "Joe Smith", Item = "Oranges", Amount = 200 },

                            new Fact() { Name = "Joe Smith", Item = "Red Bull", Amount = 4000 },

                            new Fact() { Name = "Ann Pixy", Item = "Apples", Amount = 6000 },

                            new Fact() { Name = "Ann Pixy", Item = "Oranges", Amount = 2000 },

                            new Fact() { Name = "Ann Pixy", Item = "Red Bull", Amount = 13000 },

                            new Fact() { Name = "Ann Pixy", Item = "Coke", Amount = 1000 },

                            new Fact() { Name = "David Cane", Item = "Apples", Amount = 1000 },

                            new Fact() { Name = "David Cane", Item = "Oranges", Amount = 32000 },

                            new Fact() { Name = "David Cane", Item = "Red Bull", Amount = 3000 },

                            new Fact() { Name = "David Cane", Item = "Coke", Amount = 500 },

                      };

                }

          }

    Using the pivot grid control smart tag, I'll first choose an object data source that I want to bind to. In this case I will select the Fact class. Using the smart tag again, I'll now choose the Run Designer and click on Retrieve Fields to get all the fields built for me automatically.

    See the new FieldEdit property? That's the property that will enable me to embed an in-place editor. In this case I will chose a simple calc edit.

    After binding the pivot grid control to mock data like so:

    this.pivotGridControl1.DataSource = Facts.GetMockData();

    This is what we get:

     

    Editing the value

    Editing the value is done via the EditorValueChanged event. But let's go back to our data source first. Specifically, the Joe Smith - Apples cell. Notice that the value is 3000 which is correct because I have 2 Joe Smith - Apples records in my facts table (one with the Amount of 1000 and another with the Amount of 2000) and my aggregate operation is "summary". So what should happen when I edit it? It is easy for the Ann Pixy - Red Bull because there is only one fact item. We have a couple of options here. 1) Don't allow editing in this case. 2) Show a drilled down data with only Joe Smith - Apples records or 3) back-propagate the total to each individual fact.

    Option number 3 covers the updates for single and multiple records so I will use that.

     

    private void pivotGridControl1_EditorValueChanged(object sender, DevExpress.XtraPivotGrid.EditorValueChangedEventArgs e) {

                      if (e.DataField == fieldAmount) {

                            BackProp(e.CreateDrillDownDataSource(),

                                  (decimal)e.Value,

                                  (decimal)e.Editor.EditValue);

                      }

                }

               

    // Simply distribute the amount proportionally.

    private void BackProp(PivotDrillDownDataSource dataSource,

                      decimal oldTotal, decimal newTotal) {

                      for (int i = 0; i < dataSource.RowCount; i++) {

                            decimal value = (decimal)dataSource [ i ] [ fieldAmount ];

                            value = (value / oldTotal) * newTotal;

                            dataSource [ i ] [ fieldAmount ] = value;

                      }

                }

     

    Side effects

     

    Of course having support for embedded editors means support for data validation (handled in the ValidatingEditor event). But the coolest feature here is that I can now reuse the available editors for display purposes instead of having to custom draw them. For example you might want to display a progress bar or a track bar for some fields. And all you have to do is choose it for your FieldEdit.

     

     

    Cheers

    Azret

     

More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.