Category: Programming

Resources Config File

How many of you are using XSLT? XSLT is a really powerful tool  which I haven’t been using in a while, but recently had a chance to refresh my memory while working on a project.

Sometimes, when there are some resources that are used in several places, it can be useful to keep the information of their properties in a separate file. For example, for an image it could be its path, for a video its URL etc. That way, when a property is changed, it should be updated only in one place. If this sounds like you are creating a config file for your resources – you are :)

I’m going to explain this idea on a simple example, in which the icons’ information for a html file is stored separately.

Here is the file containing icons’ aliases and paths (Icons.xml):

<?xml version="1.0" encoding="utf-8"?>
<Icons>
  <Icon name="STAR" location="Images\star.png"></Icon>
  <Icon name="DOG" location="Images\dog.png"></Icon>
  <Icon name="CLOUD" location="Images\weather_clouds.png"></Icon>
</Icons>

Each Icon node represents an icon’s properties, and in my case those are: alias (name) and image location (location). Other properties can be saved too, for example alternative text, width, height etc.

The file to be transformed (Original.xml):

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="IconsTransformer.xslt"?>
<Controls>
  <Control id="ID1" img="STAR"></Control>
  <Control id="ID2" img="DOG"></Control>
  <Control id="ID3" img="CLOUD"></Control>
</Controls>

Note: in my example, each image in the original file is represented by Control tag, while its alias is in img tag. Of course, this might not be the case in your project. Instead, you may have completely different tags and attributes to represent the resources in question. That’s not a problem, just be sure to change XSLT variables accordingly!

XSLT file (IconsTransformer.xslt):

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"                >
  <xsl:output method="html" indent="yes"/>
    <xsl:variable name="icons_var" select="document('Icons.xml')/Icons/Icon"></xsl:variable>
    <xsl:template match="Control">
      <xsl:if test="boolean(@img)">
        <xsl:variable name="current_img" select="@img"/>
        <img>
          <xsl:attribute name="src">
            <xsl:value-of select="$icons_var[@name=$current_img[1]]/@location"/>
          </xsl:attribute>
        </img>
      </xsl:if>
      <br />
    </xsl:template>
</xsl:stylesheet>

As you can see, the code is pretty straightforward. First of all, we read all the Icon nodes from the Icons.xml document and put them into icons_var variable. Then, for each control in original file, if the img attribute exists (which is actually the alias of the image declared in Icons.xml), its value is used to extract the path to the image with that alias.

The result of the transformation, in my case, looks something like this:

Xslt Result

How will this work? Well, each time an image is needed in the output html file, its alias (STAR) is set in the original file, instead of its location path (Images\star.png). This way, if an image is replaced, there is no need to do search & replace all of its occurrences in the document. Instead, only its location is changed in Icons.xml.

This simple change has shown to be very useful in my project, hopefully it’ll help you with yours!

Crystal Reports Problem With an Arabic Letter

I’m a part of the team developing a web (ASP.NET) project used by a number of customers in the Middle East. We use Crystal Reports for reporting purposes, and for the most part, it works great. However, from time to time, we run into some issues related to CR support for Arabic language.

Couple of days ago, I got to deal with a problem regarding a report that worked perfectly for several months. The report is used on daily basis by our clients and it was odd that the problem would occur now, since nothing has been changed in neither Crystal Reports file, nor the data filling it.

The issue was strange, since for some data, the report would be displayed as usual, and for the other, some characters (e.g. all zeroes in the document) were replaced with ProblemArabic1symbol. So, for example, this is how the date was shown in pdf file:

ProblemArabic

To make the issue even stranger, when that same date was copied to the clipboard and pasted into notepad, this was the result:

25-08-2014 

To my surprise, the date was there, just not properly displayed!

I was banging my head against the wall trying to understand why this was happening, until I found out that ProblemArabic1is actually a diacritic in Arabic. Then I thought the problem might have something to do with a specific letter contained in the file. Further investigation led me to alif tanwin (اً), which is basically letter alif (ا) with the tanwin diacritic. Now, I don’t speak Arabic, but I was told that alif can be used without tanwin, and that it won’t change the word meaning. After replacing اً with ا, everything started working fine!

I couldn’t find much help on the Internet, except for some forum posts stating the same thing – that the replacement should be made. Other than that, as for why the issue occurs, and is there a way to fix it, I didn’t get the answer. If anyone can shed some light on the issue, I would be grateful! :)

Crop & Save (Part II)

This is the second (and the last) post discussing cropping images and saving them to the SQL Server database, using C# ASP.NET and jQuery library called Jcrop. In my previous post, I have created a simple database model that can be used for this purpose, so if you haven’t checked it out, please do. Also, I won’t get into details with Jcrop, since its website is very informative and contains a good manual, as well as demos.

I’ve made a simple demo which can be downloaded here, and I’ll quickly guide you through it, so it could be easily understood. The idea is the following: user clicks on ‘Choose file’ button (which opens FileUpload control), picks an image on their computer and clicks on ‘Upload’ button. At that point, the image is uploaded to the app server and saved to a folder in the file system(in this demo, it is TempImages folder). User can then see the uploaded picture and do the cropping, after which ‘Crop’ button should be clicked. Then, a new bitmap will be created, containing only the area of the original photo selected as the crop area. The bitmap will then be saved to the database as the array of bytes.

FIrst of all, here is the aspx page:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="JcropDemo._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Jcrop Demo</title>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script>
    <script type="text/javascript" src="js/jquery.Jcrop.min.js"></script>
    <link rel="Stylesheet" type="text/css" href="css/jquery.Jcrop.min.css" />
    <link rel="Stylesheet" type="text/css" href="css/custom.css" />
    
    <script type="text/javascript">
        jQuery(document).ready(function() {
            
            $preview = $('#preview_pane'),
            $pcnt = $('#preview_pane .preview_container'),
            $pimg = $('#preview_pane .preview_container img'),

            xsize = $pcnt.width(),
            ysize = $pcnt.height();
            
            jQuery(window).load(function() {
                jQuery($('img[bid=EditedImage]')).Jcrop({
                    onChange: doPreviewSave,
                    onSelect: doPreviewSave,
                    boxWidth: 600,
                    boxHeight: 400,
                    aspectRatio: xsize / ysize
                }, function() {

                    // Use the API to get the real image size
                    var bounds = this.getBounds();
                    boundx = bounds[0];
                    boundy = bounds[1];
                    // Store the API in the jcrop_api variable
                    jcrop_api = $('img[bid=EditedImage]');          //this;            

                    // Move the preview into the jcrop container for css positioning
                    $preview.appendTo(jcrop_api.ui.holder);

                });
            });

            function doPreviewSave(c) {
                jQuery($('[id$=HIDDENX]')).val(c.x);
                jQuery($('[id$=HIDDENY]')).val(c.y);
                jQuery($('[id$=HIDDENW]')).val(c.w);
                jQuery($('[id$=HIDDENH]')).val(c.h);

                if (parseInt(c.w) > 0) {
                    var rx = xsize / c.w;
                    var ry = ysize / c.h;

                    $pimg.css({
                        width: Math.round(rx * boundx) + 'px',
                        height: Math.round(ry * boundy) + 'px',
                        marginLeft: '-' + Math.round(rx * c.x) + 'px',
                        marginTop: '-' + Math.round(ry * c.y) + 'px'
                    });
                }
            };
        });       
 
</script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Panel ID="CurrentImagePanel" runat="server">
            <fieldset>
                <legend id="CurrentPhotoLegend" runat="server"></legend>
                    <p>
                    <asp:Image ID="UserImage" runat="server" CssClass="user_photo_large" />
                    <asp:FileUpload ID="ImageUpload" runat="server" />
                </p>
                <p>
                    <asp:Button ID="ImageUploadButton" runat="server" onclick="ImageUploadButton_Click" Text="Upload"/>
                </p>    
                <p>
                    <asp:Label ID="ImageUploadErrorLabel" runat="server"></asp:Label>
                </p>
            </fieldset>            
        </asp:Panel>
        <asp:Panel ID="EditedImagePanel" runat="server">   
            <fieldset>
                <legend><asp:Label ID="EditedPhotoLabel" runat="server"></asp:Label></legend>
                <asp:Image ID="EditedImage" runat="server" bid="EditedImage" />
                <div id="preview_pane">
                    <div class="preview_container">
                        <asp:Image ID="PreviewImage" runat="server" CssClass="jcrop-preview" />
                    </div>
                </div>          
                <p style="clear:both">
                    <asp:Button ID="CropImageButton" runat="server" OnClientClick="return checkCroppedImage();" onclick="CropImageButton_Click" CssClass="custom_button3" Text="Crop"/>
                    <asp:HiddenField ID="HIDDENX" runat="server" />
                    <asp:HiddenField ID="HIDDENY" runat="server" />
                    <asp:HiddenField ID="HIDDENW" runat="server" />
                    <asp:HiddenField ID="HIDDENH" runat="server" />
                </p>  
            </fieldset>              
        </asp:Panel>
    </div>
    </form>
</body>
</html>

So, we have to add references to jQuery and jQueryui, as well as to Jcrop js and css files. There are two panels, CurrentImagePanel, shown before the image has been selected, and EditedImagePanel, where the cropping will take place. Hidden fields HIDDENX, HIDDENY, HIDDENW and HIDDENH are used to store coordinates of the rectangle representing new (cropped) image. On window load, Jcrop’s default behavior is activated. When the selection is made, hidden fields are populated with its values (check doPreviewSave function).

Code behind:

using System;
using System.Linq;
using System.IO;
using System.Web;

namespace JcropDemo
{
    public partial class _Default : System.Web.UI.Page
    {
        private string[] _AllowedExtensions = { ".png", ".jpeg", ".jpg", ".gif" };
        private string _TempImageLocation = "TempImages\\";
        private string _ImagePath = HttpContext.Current.Request.PhysicalApplicationPath + "TempImages\\";
        private int _UserID = 1;

        protected void Page_Load(object sender, EventArgs e)
        {
            if (Session["WorkingImage"] != null)
            {
                EditedImagePanel.Visible = true;
                CurrentImagePanel.Visible = false;

                PreviewImage.ImageUrl = EditedImage.ImageUrl = _TempImageLocation + (string)Session["WorkingImage"];
            }
            else
            {
                EditedImagePanel.Visible = false;
                CurrentImagePanel.Visible = true;

                UserImage.ImageUrl = _TempImageLocation + "noimage.jpg";
            }
        }

        protected void ImageUploadButton_Click(object sender, EventArgs e)
        {
            string imageName = "";
            if (ImageUpload.HasFile)
            {
                string extension = System.IO.Path.GetExtension(ImageUpload.FileName).ToLower();
                if (_AllowedExtensions.Contains(extension))
                {
                    try
                    {
                        imageName = string.Format("{0}_{1}{2}", _UserID, DateTime.Now.ToFileTimeUtc(), extension);
                        ImageUpload.SaveAs(_ImagePath + imageName);

                        EditedImagePanel.Visible = true;
                        CurrentImagePanel.Visible = false;

                        PreviewImage.ImageUrl = EditedImage.ImageUrl = _TempImageLocation + imageName;

                        Session["WorkingImage"] = imageName;
                    }
                    catch (Exception ex)
                    {
                        ImageUploadErrorLabel.Text = "Image could not be uploaded. Details: " + ex.Message;
                    }
                }
                else
                {
                    ImageUploadErrorLabel.Text = "Wrong extension!!!";
                }
            }
        }

        protected void CropImageButton_Click(object sender, EventArgs e)
        {
            if (Session["WorkingImage"] == null)
                return;

            string imageName = Session["WorkingImage"].ToString();

            int w = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENW.Value)));
            int h = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENH.Value)));
            int x = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENX.Value)));
            int y = Convert.ToInt32(Math.Floor(Convert.ToDouble(HIDDENY.Value)));

            byte[] cropImage = Crop(_ImagePath + imageName, w, h, x, y);

            BLL.SaveUserProfilePhoto(_UserID, cropImage);

            File.Delete(_ImagePath + imageName);
            Session["WorkingImage"] = null;

            EditedImagePanel.Visible = false;
            CurrentImagePanel.Visible = true;
        }

        static byte[] Crop(string Img, int Width, int Height, int X, int Y)
        {
            try
            {
                using (System.Drawing.Image originalImage = System.Drawing.Image.FromFile(Img))
                {
                    using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(Width, Height))
                    {
                        bmp.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
                        using (System.Drawing.Graphics Graphic = System.Drawing.Graphics.FromImage(bmp))
                        {
                            Graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                            Graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                            Graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                            Graphic.DrawImage(originalImage, new System.Drawing.Rectangle(0, 0, Width, Height), X, Y, Width, Height, System.Drawing.GraphicsUnit.Pixel);
                            MemoryStream ms = new MemoryStream();
                            bmp.Save(ms, originalImage.RawFormat);
                            return ms.GetBuffer();
                        }
                    }
                }
            }
            catch (Exception Ex)
            {
                throw (Ex);
            }
        }
    }
}

In order for this example to be as simple as possible, there is only one user that the image would be added to, hence the variable _UserID is set to 1. In the ImageUploadButton_Click event, the image is saved to a folder on the server. In CropImageButton_Click, the points of the cropped rectangle (represented in the hidden fields) are used to create array of bytes that is later saved to the database. On successful save, the original image is deleted from the file system.

I’ve put the business logic into separate file:

using System;
using System.Configuration;
using System.Linq;
using System.Web;

namespace JcropDemo
{
    public class BLL
    {
        public static Model Model
        {
            get
            {
                if (!HttpContext.Current.Items.Contains("Model"))
                    HttpContext.Current.Items.Add("Model", new Model());

                return (Model)HttpContext.Current.Items["Model"];
            }
        }

        public static void SaveUserProfilePhoto(int userID, byte[] photoContent)
        {
            User usr = Model.Entities.User.Where(x => x.UserId == userID).FirstOrDefault();
            if (usr != null)
            {
                Image img = new Image();
                img.ImageContent = photoContent;
                img.User.Add(usr);

                Model.Entities.AddToImage(img);
                Model.Entities.SaveChanges(true);
            }
        }
    }

    public class Model
    {
        private TestDbEntities _Entities = null;
        public TestDbEntities Entities
        {
            get
            {
                if (_Entities == null)
                {
                    if (ConfigurationManager.ConnectionStrings["TestDbEntities"] == null)
                    {
                        throw new NullReferenceException("Missing connection string!");
                    }
                    _Entities = new TestDbEntities(ConfigurationManager.ConnectionStrings["TestDbEntities"].ConnectionString);
                }
                return _Entities;
            }
        }
    }
}

Static method SaveUserProfilePhoto uses EF model to save the data to the SQL Server database.

So, once again: you can download the source code here, and remember to change your connection settings in Web.config!

Hope this example is useful, and I’ll be glad to get your feedback on it.

Crop & Save (Part I)

Hi all,

I’ve neglected the blog for some time because of the projects I’m currently involved in, but I’ll do my best to write more, I promise! :)

If you make code, you’ve probably at least once had the requirement to save an image in the system. For example, user can upload their profile photo so it could be displayed throughout your application. Let’s see how that could be done in C# ASP.NET, using SQL Server and Entity Framework. Also, we’ll add a cool feature of cropping photos, for which we’ll use jQuery plugin called JCrop. Since this is a fairly long example, I will divide it into couple of posts, first one discussing the database structure.

The database model for this use case is really a basic one. There are three tables: User – where the user information is stored, Image – the same for the photos/images to be uploaded, and UserImage – composite table, containing only primary keys for User and Image tables. Here is the database diagram:

DbModel

Note: User table structure is taken from a project I work on, so the fields in it reflect that app’s requirements. Some other applications may require different fields. In this example, the only field that will actually be used is UserId. I’ve added only one user to the table, having UserId set to 1. Have this in mind when going through this example!

User:

CREATE TABLE [dbo].[User](
       [UserId] [int] IDENTITY(1,1) NOT NULL,
       [Username] [nvarchar](50) NOT NULL,
       [Password] [varbinary](32) NULL,
       [FirstName] [nvarchar](50) NULL,
       [LastName] [nvarchar](50) NULL,
       [Activated] [bit] NULL,
       [Email] [nvarchar](100) NOT NULL,
       [Birthday] [datetime] NULL,
       [GenderId] [int] NULL,
       [DateCreated] [datetime] NULL,
       [DateUpdated] [datetime] NULL,
       [IsDeleted] [tinyint] NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
       [UserId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Image:

CREATE TABLE [dbo].[Image](
       [ImageId] [int] IDENTITY(1,1) NOT NULL,
       [ImageContent] [varbinary](max) NOT NULL,
CONSTRAINT [PK_Image] PRIMARY KEY CLUSTERED
(
       [ImageId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

UserImage:

CREATE TABLE [dbo].[UserImage](
       [UserId] [int] NOT NULL,
       [ImageId] [int] NOT NULL,
CONSTRAINT [PK_UserImage] PRIMARY KEY CLUSTERED
(
       [UserId] ASC,
       [ImageId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[UserImage]  WITH CHECK ADD  CONSTRAINT [FK_UserImage_Image] FOREIGN KEY([ImageId])
REFERENCES [dbo].[Image] ([ImageId])
GO

ALTER TABLE [dbo].[UserImage] CHECK CONSTRAINT [FK_UserImage_Image]
GO

ALTER TABLE [dbo].[UserImage]  WITH CHECK ADD  CONSTRAINT [FK_UserImage_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([UserId])
GO

ALTER TABLE [dbo].[UserImage] CHECK CONSTRAINT [FK_UserImage_User]
GO

Here is the corresponding EF model:

EFModel

Note: If you’re wondering where the UserImage table has disappeared, remember that it is an aggregate table, so in EF model it won’t be represented as entity type, but as association!

Association

In order for this example to be simple, we’ll save only one user to the database and make sure its id is set to 1:

SET IDENTITY_INSERT [User] ON

INSERT INTO [User](UserId, Username, Email)
VALUES (1, 'test', 'test@example.com')

SET IDENTITY_INSERT [User] OFF

When the database is set, we can move on to programming a simple ASP.NET application for saving the images. You can check how it is done in this post.

Ext.NET Grid Issue

Hello all! Sorry for not posting more often, but I don’t have lots of spare time these days, since I’m working on couple of projects… :( I’ll definitely do my best in writing more!

Today I would like to share an unusual issue I’ve had at work these days, regarding Ext.NET grid control. For you unfamiliar with it, Ext.NET is an ASP.NET component framework integrating the Ext JS library. In my company, we have created an ASP.NET C# application using Ext.NET controls. I have encountered an issue occurring only in one grid, which has both combobox and button in the row. There is some processing on mousedown event, since the row must be selected when the button is clicked. Now, what happened was that, when the combobox was clicked on, it wouldn’t display the values. It looked something like this:

grid on mousedown

But, when navigating through items using keyboard, one could see all the values:

grid on keydown

I’ve been trying to solve the problem, and thought the issue was that the combobox event was somehow overridden. Finally, when I couldn’t solve it, I’ve asked the question on stackoverflow, hoping that someone would help, and created a simplified example in which the behavior could be reproduced. Here it is:

<%@ Page Language="C#" AutoEventWireup="true" %>

<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script type="text/javascript">
    function setGridCurrentRow(grid, e) {
        var parent = Ext.fly(e.target).findParent(grid.getView().rowSelector, grid.getView().rowSelectorDepth);

        if (parent) {        // if no row selected
            grid.currentRow = grid.store.getRange()[parent.rowIndex].data;
        }
        else {               // else - row selected
            grid.currentRow = grid.getRowsValues()[0];
        }
    }
</script>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!X.IsAjaxRequest)
        {
            Ext.Net.ConfigItem cItem = new Ext.Net.ConfigItem();
            cItem.Mode = Ext.Net.ParameterMode.Raw;
            cItem.Name = "listeners";
            cItem.Value += "{'keydown':function(e){ if(e.getKey()==9){setGridCurrentRow(" + TestGrid.ID + ",e);}}";
            cItem.Value += ",'mousedown':function(e){ if(e.target != null) setGridCurrentRow(" + TestGrid.ID + ",e);}";
            cItem.Value += ",'command':function(command,gridRecord){alert('Here I am!');}}";
            TestGrid.CustomConfig.Add(cItem);

            TestStore.DataSource = Data;
            this.TestStore.DataBind();
        }
    }

    private object[] Data
    {
        get
        {
            return new object[]
            {
                new object[] { 1, "f", "First", "" },
                new object[] { 2, "s", "Second", "" },
                new object[] { 3, "f", "First", "" },
                new object[] { 4, "t", "Third", "" },
                new object[] { 5, "f", "Fourth", "" },
            };
        }
    }

</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
     <ext:ResourceManager ID="ResourceManager1" runat="server" />
    <form id="form1" runat="server">
    <div>
        <ext:Store ID="TestStore" runat="server" >
            <Reader>
                <ext:ArrayReader IDProperty="ID" >
                    <Fields>
                        <ext:RecordField Name="ID"></ext:RecordField>
                        <ext:RecordField Name="TestText"></ext:RecordField>
                        <ext:RecordField Name="TestValue"></ext:RecordField>
                        <ext:RecordField Name="ButtonText"></ext:RecordField>
                    </Fields>
                </ext:ArrayReader>
            </Reader>
        </ext:Store>
        <ext:GridPanel ID="TestGrid" runat="server" StoreID="TestStore"  Width="600" Height="350" ClicksToEdit="1" TrackMouseOver="true" Selectable="true" >
            <ColumnModel ID="TestModel">
                <Columns>
                    <ext:Column ColumnID="TestText" Header="TestText" MenuDisabled="true">
                        <Editor>
                            <ext:TextField ID="TestText_Text" runat="server"></ext:TextField>
                        </Editor>
                    </ext:Column>
                    <ext:Column ColumnID="TestValue" Header="Value" MenuDisabled="true">
                        <Editor>
                            <ext:ComboBox ID="TestValue_Combo" runat="server" Editable="false" ForceSelection="true">
                                <Items>
                                    <ext:ListItem Text="First" Value="1" />
                                    <ext:ListItem Text="Second" Value="2" />
                                    <ext:ListItem Text="Third" Value="3" />
                                    <ext:ListItem Text="Fourth" Value="4" />
                                    <ext:ListItem Text="Fifth" Value="5" />
                                </Items>
                            </ext:ComboBox>
                        </Editor>
                    </ext:Column>
                    <ext:Column ColumnID="TestButton" Header="Edit" MenuDisabled="true">
                        <Commands>
                            <ext:ImageCommand Icon="ApplicationEdit" CommandName="Edit" Text="Edit" ></ext:ImageCommand>
                        </Commands>
                    </ext:Column>
                </Columns>
            </ColumnModel>
            <SelectionModel >
                <ext:CheckboxSelectionModel ID="TestSelectionModel" runat="server" ></ext:CheckboxSelectionModel>
            </SelectionModel>
        </ext:GridPanel>
    </div>
    </form>
</body>
</html>

Fortunately, I was helped by this guy. Seems this was the problem:

grid.getRowsValues()[0];

and should have been changed with this line of code:

grid.getStore().getAt(0).data;

This solved the issue. Thanks Daniil! :)