Разработка электроники,

Систем автоматики,

Программного обеспечения

8(981)186-50-82

ООО "Антех ПСБ",
Санкт-Петербург

anteh@bk.ru

antehru@gmail.com

©

Визуализация перетаскивания столбцов, строк расширенного элемента GridView. Динамическое создание курсоров .gif и .cur для имитации helper. Преобразование форматов .ico в .cur. jQuery C# ASP.NET VS2008

Сайт https://anteh.ru

К слову 1:
VS2008. Если в solution explorer присутствует какой-либо выгруженный проект, то разместить AJAX Toolkit элементы и созданный server control в визуальном редакторе на .aspx странице не получается. Причём некоторые стандартные элементы можно было разместить. После удаления выгруженного проекта, или его обратной загрузки опять стало возможным добавлять элементы, через визуальный редактор.

К слову 2:
"Ошибка выполнения Microsoft JScript: "TestServContr" не определено"1

VS2008. Такое происходило из-за отсутствия в проекте серверного контрола управления описания клиентского класса. Т.е. если наследуем разрабатываемый контрол от штатного контрола, например Button(public class tbutt : Button, IScriptControl), используются .js скрипты и их регистрация производится через интерфейс IScriptControl. То обязательно должен присутствовать .js файл с описанием клиентского класса. Скрипты могут размещаться например в другом файле. Или другими словами: проект контрола можем создавать как ASP.NET Server Control или ASP.NET AJAX Server Control. В проекте ASP.NET AJAX Server Control по умолчанию создаётся нужный клиентский класс. Но класс этого контрола уже унаследован от класса ScriptControl, поэтому унаследовать его от Button или GridView и т.д. не получится, только с помощью интерфейсов. Разумеется каждый делает так, как считает правильным. Для себя, удобным путь создания серверного контрола управления вижу следующий: создаём проект ASP.NET Server Control, класс контрола наследуем от расширяемого штатного элемента, например от GridView, добавляем и реализуем интерфейс IScriptControl. Из проекта ASP.NET AJAX Server Control берём .js файл клиентского класса как пример реализации и помещаем его в ASP.NET Server Control проект, меняя всё что потребуется.

Статья -что-то из серии, поиска решений, не всегда удачных по началу, или для чего нужен jQuery, jQuery UI. Приведённый код не претендует на оригинальность, но проверен и брался прямо из рабочей сборки решения. Прилагается пример созданного EGridView контрола, демонстрирующий результат. Это расширенный серверный контрол GridView с реализованной визуализацией перетаскивания столбцов, строк, AJAX обмен данными с сервером был удалён, чтобы не загромождать пример.Но если кому потребуется здесь статья и пример проекта по организации AJAX обмена.

Сначала неудачный пример. Речь о визуализации перетаскивания столюцов и строк GridView. Динамическом создании новых графических изображений курсоров. Рассмотрен случай визуализации перетаскивания столбцов, путём изменения графического изображения курсора на текст из ячеек заголовка GridView представленный в виде графического изображения. Т.е. некий аналог helper draggable для ячейки заголовка создан подменой графического изображения курсора. Затем то же самое будет реализовано через jQuery, без недостатков первого способа.
Для первого случая всё свелось к созданию текста заголовка столбца в виде картинки(Bitmap) и преобразования полученного изображения в .gif и .cur форматы курсора для Internet Explorer, Firefox, Chrome, Safari, Yandex. Opera с дополнительными файлами курсора не работает. Автоматически создаваемые Файлы .gif и .cur хранятся в отдельной папке web приложения.
Для расширения функциональности GridView создан конторол наследуемый от GridView public class EGridView : GridView . Одним из таких расширений была визуализация перетаскивания столбца GridView. При нажатии и удержании левой кнопки мыши над какой-либо ячейкой заголовка GridView, с последующим перетаскиванием, значок указателя мыши f менялся на текст из ячейки заголовка столбца - 3, 4, 5 и т.д. Текст представлен в виде изображения .gif или .cur. На странице может находиться несколько таких контролов. Изображения курсоров заранее в ручную не формируются -заранее не известно название заголовка столбца, количество столбцов и названия заголовков может динамически меняться.
Конечно можно воспользоваться стандартными средствами для реализации функции визуального перетаскивания, но как часто бывает, по началу были свои причины.
Internet Explorer в качестве формата файла курсора поддерживает CUR и ANI. Firefox, Chrome, Safari поддерживают форматы CUR, PNG, GIF, JPG. Yandex поддерживает минимум: CUR, GIF -остальные не проверял.
Opera, на текущий момент, не работает с файлами курсора.
Небольшая информация на 06.01.2013. Проверял работу контрола в разных браузерах только для .cur и .gif форматов файлов курсора:

//Здесь живые примеры курсоров автоматически поддерживаемых браузерами http://www.w3schools.com/cssref/playit.asp?filename=playcss_cursor&preval=url(smiley.gif),url(myBall.cur),auto

//Описание синтаксиса cursor: [url]
//cursor: url('../cursors/customMoveCursor.cur'), // Modern browsers
// url('cursors/customMoveCursor.cur'), // Internet Explorer
// move; // Older browsers. Дефолтное значение, если не первое и не второе. Можно указать и большее количество альтернативных курсоров.
//Button1.Attributes.Add("style", "cursor:url(cursor2.gif),url(cursor2.cur),crosshair;"); //cursor2.gif и cursor2.cur находятся в корневой папке приложения вместе с Default.aspx файлом
//Explorer (9.08112.16421) в качестве файла указателя может использовать только *.cur файлы
//Firefox 43Mb (17.0.1) как *.cur так и .gif файлы
//Safari 104Mb (5.1.5) как *.cur так и .gif файлы
//Opera (1212) Ни *.cur, ни .gif не работают и не работали, для установки удаления требует подключения к интернету, проблемы с удалением закладок, если закладок много, то все разом не удалиш, при установке нет галки не импортировать закладки. Закладки импортирует без спроса. Возможно информацию о ваших закладках опера использует в своих целях. Злодеи однозначно. И это-то при первом поверхностном 5минутном осмотре обыкновенным пользователем.
//Yandex 228Mb (Версия 1.5.1104.222 (e49708c)) поддерживает как .cur так и .gif файлы. Напрягает установка не из дистрибутива и не в папку program files, а неизвестно куда C:\Users\ляля\AppData\Local\Yandex\YandexBrowser\Application\browser.exe И название исполняемог файла browser.exe глаз режет, yandex.exe было бы понятнее. Без спросу закладки не импортирует. Внешне чем-то на Safari похож в общем всё одно webkit.
//Chrome () -Не проверял, но уверен там всё ОК.


Рассматривается общий случай, когда текст заголовка столбца может измениться когда угодно и соответственно новое изображение курсора должно быть динамически создано. В общем контрол сам создаёт папку, в которой будут храниться курсоры .cur и gif. Контрол создаёт папку в статическом конструкторе. Размер одного файла курсора 1-3 килобайта в среднем для небольшого заголовка столбца. Т.е. при перезапуске web приложения файлы в папке crs удаляются. Папка изображениями курсоров заполняется автоматически самим контролом, при необходимости. Ниже приведён код статического конструктора контрола EGridView

private static Hashtable HdClIm = Hashtable.Synchronized(new Hashtable()); //Содержит ссылки на изображения курсоров для ячеек заголовка таблицы
private const string CursorImageFolder = "crs"; //Название папки в корневой директории приложения хранящей курсоры
private static string MainDir = AppDomain.CurrentDomain.BaseDirectory;
static EGridView()
{
    //Если папки CursurImageFolder не существует, то создаём новую, 
    //если существует, то удаляем все файлы из неё
    if (Directory.Exists(MainDir + CursorImageFolder))
    {   try
        {   string[] ss = Directory.GetFiles(MainDir + CursorImageFolder);
            foreach (string s in ss) File.Delete(s);
            //Directory.Delete(MainDir + CursorImageFolder, true); //Удаление папки со всеми вложениями
        }catch (Exception ex) { if (ex == null) { } }
    }
    else        //if (!this.DesignMode)
    {   try { Directory.CreateDirectory(MainDir + CursorImageFolder); }
        catch (Exception ex) { if (ex == null) { } }
    }
}

Для формирования графического изображения текста заголовка используется только текст заголовка и размер emSize = 20. Размер графического представления текста фиксирован. Чёрный текст на белом фоне. Файлы .cur и gif для каждого запуска web приложения создаются только один раз и при необходимости. Информация о имени файла и директории его размещения хранится в статической синхронной HashTable HdClIm. Если при запущенном web приложении из папки crs удалить один или несколько или все файлы, то контрол их сам по необходимости восстановит -это штатная ситуация.

В кратце реализованное решение:
В protected override void Render(System.Web.UI.HtmlTextWriter writer) текущего контрола:

string onMouseDown = Page.ClientScript.GetPostBackClientHyperlink(this, "HdCl_down$" + i.ToString() + "$" + this.HeaderRow.Cells[i].ClientID, true) + ";";
this.HeaderRow.Cells[i].Attributes.Add("onmousedown", onMouseDown);
string onMouseUp = Page.ClientScript.GetPostBackClientHyperlink(this, "HdCl_up$" + i.ToString() + "$" + this.HeaderRow.Cells[i].ClientID, true) + ";";
this.HeaderRow.Cells[i].Attributes.Add("onmouseup", onMouseUp);

Здесь в цикле, для каждой ячейки заголовка GridView задаём реакцию на нажатие и удержание левой кнопки мыши onmousedown, и реакцию на отпускание onmouseup. HdCl_down -название команды, которая будет ловиться в protected override void OnRowCommand(GridViewCommandEventArgs e) текущего контрола. Название выдумываем сами, оно не должно совпадать с зарезервированными названиями, например "Page". Первый знак "$" следующий за названием команды -разделитель между командой и последующими передаваемыми данными. Передаётся i.ToString() -номер столбца ячейки заголовка для дела пока нигде не используется, "$" -использовался как разделитель, далее this.HeaderRow.Cells[i].ClientID -это идентификатор ячейки по которой произвели нажатие мышкой тоже пока нигде не используется можно эти поля вообще исключить. Код задающий подсветку каждой ячейки заголовка столбца, при наведении и строки данных, а также смена указателя мыши на другие штные курсоры, запрет выделения в заголовке таблицы и остальной не относящийся к делу код опускается.

Ниже приведён полный, как был на тот момент код функции OnRowCommand. Здесь и происходит основное действо по формированию новых изображений курсоров.

protected override void OnRowCommand(GridViewCommandEventArgs e)
{
    if (e.CommandName == "HdCl_down") //Ячейки заголовка событие нажатия и удерживания левой кнопки мыши
    {
        string str = Convert.ToString(e.CommandArgument);
        string[] stt = str.Split(new char[1] { '$' });
        MouseDownHeaderCellIndex = Convert.ToInt32(stt[0]);
        MouseDownHeaderCellID = stt[1];
        //<<<=== Формирование соответствующего курсора для текущей ячейки заголовка
        string text = this.HeaderRow.Cells[MouseDownHeaderCellIndex].Text;
        if ((!HdClIm.Contains(text + "gif")) | (!HdClIm.Contains(text + "cur")))
        {
            Font font = new Font(this.HeaderStyle.Font.Name, 20, FontStyle.Regular, GraphicsUnit.Pixel);
            Graphics graphics = Graphics.FromImage(new Bitmap(1, 1));
            int width = (int)graphics.MeasureString(text, font).Width;
            int height = (int)graphics.MeasureString(text, font).Height;
            Bitmap bmp = new Bitmap(width, height);
            graphics = Graphics.FromImage(bmp);
            graphics.Clear(Color.White);
            //graphics.SmoothingMode = SmoothingMode.AntiAlias;
            //graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
            graphics.DrawString(text, font, new SolidBrush(Color.Black), 0, 0);
            //graphics.Flush(); graphics.Dispose();
            //получаем .cur файл
            Icon icon = Icon.FromHandle(bmp.GetHicon());
            MemoryStream bms = new MemoryStream();
            icon.Save(bms);
            byte[] curf = bms.ToArray();
            GetCurFromIcon(ref curf, 1, 1); //преобразуем массив байт формата .ico к формату .cur
            FileStream fs = new FileStream(MainDir + CursorImageFolder + "\\" + text + ".cur", FileMode.Create, FileAccess.Write);
            fs.Write(curf, 0, curf.Length); fs.Close();
            if (!HdClIm.Contains(text + "cur")) HdClIm.Add(text + "cur", CursorImageFolder + "/" + text + ".cur");
            //получаем .gif файл
            bms = new MemoryStream();
            bmp.Save(bms, System.Drawing.Imaging.ImageFormat.Gif);
            byte[] bt = bms.ToArray();
            fs = new FileStream(MainDir + CursorImageFolder + "\\" + text + ".gif", FileMode.Create, FileAccess.Write);
            fs.Write(bt, 0, bt.Length); fs.Close();
            //if (!HdClIm.Contains(text + "gif")) HdClIm.Add(text + "gif", (TempDir + CursorImageFolder + "\\" + text + ".gif").Replace("\\", "/"));
            if (!HdClIm.Contains(text + "gif")) HdClIm.Add(text + "gif", CursorImageFolder + "/" + text + ".gif");
            //byte[] bmpH = new SHA256CryptoServiceProvider().ComputeHash(bt); //вычисляем Hash код изображения курсора
            //string fnm = BitConverter.ToString(bmpH).Replace("-", string.Empty); //Преобразуем bmpH к строке
            //string base64 = Convert.ToBase64String(bmpH);
        }
        //this.HeaderRow.Attributes.Remove("style");
        string scr = (string)HdClIm[text + "cur"];
        string sgf = "../" + (string)HdClIm[text + "gif"]; //"../" -с или без этой строки разницы ни какой
        this.HeaderRow.Attributes.Add("style", "cursor:url(" + scr + "),url(" + sgf + "),crosshair;"); //crosshair будет в опере, так как она не поддерживает курсоры подгружаемые из картинок
    }
    if (e.CommandName == "HdCl_up") //Ячейки заголовка событие отпускания кнопки изображение курсора возвращаем в исходное
    {
        //this.HeaderRow.Attributes.Remove("style");
        this.HeaderRow.Attributes.Add("style", "cursor:move;");
    }
    base.OnRowCommand(e);
}

//Преобразование байтового массива .ico файла к байтовому массиву .cur файла
//ОТЛИЧИЯ МЕЖДУ .ico и .cur файлами: wPlanes, wBitCount, idType
//1. idType для иконки =1, для файла курсора =2
//2. В случае с файлами, которые имеют расширение ico, поле wPlanes задаёт количество цветовых плоскостей и, как правило, равняется 1, 
//      а поле wBitCount определяет битовый формат пиксела, а именно: количество бит, которое требуется для описания цвета одного пиксела. 
//      Для cur-файлов эти два поля содержат координаты так называемой "горячей точки" (hotspot) курсора, в первом из них задаётся его X-, а во втором - Y-координата. На этом все отличия между файлами курсоров и иконок заканчиваются
/*struct ICONDIR //заголовочная часть файла .ico
{
    public UInt16 idReserved;   //всегда должен быть 0
    public UInt16 idType;       //idType для иконки =1, для файла курсора =2
    public UInt16 idCount;      //idCount определяет, какое количество иконок содержит файл
    public ICONDIRENTRY[] idEntries;// = new ICONDIRENTRY[idCount];
}
struct ICONDIRENTRY //Информация о ресурсе
{
    byte bWidth;            //bWidth, bHeight определяют геометрические размеры значка в пикселах
    byte bHeight;
    byte bColorCount;       //количество цветов, использующихся в значке
    byte bReserved;         //зарезервировано для будущих версий Windows и всегда равно 0
    UInt16 wPlanes;         //для .ico поле wPlanes задаёт количество цветовых плоскостей и, как правило, равняется 1
    UInt16 wBitCount;       //для .ico определяет битовый формат пиксела, а именно: количество бит, которое требуется для описания цвета одного пиксела
                            //Для .cur файлов эти два поля содержат координаты так называемой "горячей точки" (hotspot) курсора, в первом из них задаётся его X-, а во втором - Y-координата
    UInt32 dwBytesInRes;    //размер соответствующего ресурса значка в байтах
    UInt32 dwImageOffset;   //смещение в байтах от начала файла до соответствующего ресурса в нём
}*/
private void GetCurFromIcon(ref byte[] icon, UInt16 hotspot_x, UInt16 hotspot_y)
{
    if (icon != null)
    if (icon.Length > 3)
    {
        icon[0] = 0; icon[1] = 0; //на всякий
        icon[2] = 2; icon[3] = 0; //idType = 2 для .cur файлов
        icon[4] = 1; icon[5] = 0; //1 изображение в файле курсора
        icon[10] = (byte)(hotspot_x & 0x00ff); //hotspot_x wPlanes
        icon[11] = (byte)(hotspot_x >> 8);
        icon[12] = (byte)(hotspot_y & 0x00ff); //hotspot_y wBitCount
        icon[13] = (byte)(hotspot_y >> 8);
    }
}

GetCurFromIcon -если в кратце, то для преобразования нужно второй байт .ico поменять с 1 на 2. Есть ещё 2 параметра -//hotspot_x wPlanes и //hotspot_y wBitCount, которыми отличаются эти 2 формата файлов.

Можно упомянуть про нюанс со странным поведением web приложения, при удалении создании переименовании папки в корне текущего сайта происходит перезапуск web приложения. Здесь это crs папка и нюансы возникают при использовании методов Directory.Delete Directory.CreateDirectory. Как выяснилось -это нормальное поведение и как было заявлено, является не 'багом', а 'фичей'. Чтобы этого избежать папка crs должна находиться во временной папке используемой операционной системы.

Это решение помогло, но не пригодилось cursor:url капризная штука оказывается. Размещать crs пришлось всё-таки в папке приложения, и удалять не саму папку вместе с файлами, а только сами файлы в папке. Как оказалось, с временной папки, читаются только .cur файлы. И курсор адекватно подменяется на картинку с текстом только в explorer, .gif остальными браузерами не читался, думаю из за наличия в пути к файлу .gif таких вот символов 'С:', в общем разбираться в нюансах желания не было. После возвращения crs в корень и отказа от её удаления/создания всё стало отрабатывать правильно.

Может кому пригодиться:12

Невозможно задать 'asp:UpdatePanel' для свойства 'ContentTemplate'. Это когда контрол находился в MultiView->View->Table->Cell->div->UpdatePanel13

Инициализатор типа "…" выдал исключение. Это тот же самый контрол находящийся в Table->Cell

К подобным ошибкам приводила строка 'Directory.CreateDirectory(MainDir + CursorImageFolder);' в static EGridView(). Если строку обернуть Try Catch или использовать if (!this.DesignMode), то всё будет в порядке. В общем ошибка возникала из за невозможности доступа к какой-то директории студии, при инициализации контрола визуальным редактором. Тоже касается и переименования с удалением.

В конечном итоге как задуманно контрол работал в Explorer и Firefox, на всех браузерах на WebKit движке работало неправильно. Для WebKit браузеров подмена курсора происходила с недопустимо большой задержкой и через раз, наблюдалось мерцание изображения курсора при нажатии левой кнопки мыши. Изображение менялось со штатного указателя на новую картинку и обратно, причём штатный курсор выпадал большее количество раз -отсюда задержка. Как выяснилось в последствии, причина проблем с курсором для WebKit могла крыться в произвольной обработке событий 'on'…, т.е. onmousedown и onmouseup для одной и той же ячейки заголовка выполнялись в произвольном порядке. Видел рекомендацию не вешать несколько событий объявленных через 'on' на один web элемент -на что и напоролся. Да и штатный контрол Menu работал криво -при наведении мыши не происходило выпадение меню, приходилось клацать мышью и элементы меню представлялись в виде сплошного текста -там наверно со стилем чего-то не того получалось. При событии "onmousedown" "onmouseup" над любой из ячеек заголовка происходит слишком много "телодвижений" со стороны сервера, и это всё происходило ради функционально незначительной визуализации перетаскивания столбца GridView.

Есть следующее наблюдение: В IE смена курсора мыши на картинку происходит только после: 1. Полной остановки курсора мыши над ячейкой заголовка GridView 2. Нажатием-удержанием левой кнопки мыши над ячейкой 3. Начала перемещения мыши. Только при соблюдении этих условий происходит подмена графического изображения указателя мыши. Если остановки мыши над ячейкой не было, и нажатие левой кнопки мыши над ячейкой происходит в движении, то подмены изображения курсора не происходит. В общем, слишком много граблей.

А теперь рассмотрим то, как оно должно быть на самом деле. Рассмотрим второй случай. Всю визуализацию подсветки строк ячеек столбцов, перетаскивания столбца и прочее возложим на сторону клиента посредством JavaScript. И чтоб проблем с кросс браузерностью было как можно меньше, а возможностей побольше, используем jQuery. jQuery файл библиотеки скачиваем с официального сайта http://jquery.com/ jquery-1.8.3.min.js -на тот момент. Далее подключаем его к проекту контрола в качестве встроенного ресурса. Как произвести подключение картинки, .css .js файлов в качестве встроенных ресурсов описано в этой статье. Пример проекта демонстрирующего подключение .js как встроенного ресурса здесь. Если вкратце, то копируем файл jquery-1.8.3.min.js в проект контрола, например в папку КореньПроекта/eresource/js/, в свойствах файла jquery-1.8.3.min.js(переименован в jquery183min.js) отмечаем, что файл будет встроенным ресурсом сборки(embedded resource). На картинке видна структура папок и что откуда берётся.14

Добавляем строку-атрибут […] в .cs файл проекта серверного элемента управления перед namespace…. Или в файл AssemblyInfo.cs

[assembly: System.Web.UI.WebResource("ServControlEGridView.eresource.js.jquery183min.js", "text/javascript")]

Далее регистрируем библиотеку на странице, делаем это через интерфейс IScriptControl, объявление класса контрола выглядит следующим образом: public class EGridView : GridView, IScriptControl

Реализуем 2 функции интерфейса и добавляем некоторый код в OnPreRender и Render, подробности в вышеупомянутой статье. Регистрацию .js можно произвести и другими способами.

Нужно убедиться, что все таблицы стилей подключаются перед скриптами. Это позволит корректно объявить все свойства элементов перед выполнением кода jQuery. Если этого не сделать, то возможна некорректная работа в некоторых браузерах, особенно в тех, которые основаны на WebKit движке. Подобное реализовано в OnPreRender, код ниже:

protected override void OnPreRender(EventArgs e)
{
    //CSS СТИЛИ ДОЛЖНЫ ПОДКЛЮЧАТЬСЯ РАНЬШЕ СКРИПТОВ ИХ ИСПОЛЬЗУЮЩИХ иначе могут возникнуть нюансы минимум с браузерами на WebKit движке Safari и т.п.
    if (!this.Page.ClientScript.IsClientScriptBlockRegistered("JsCssReg67")) //Чтобы несколько раз, на одной странице, одно и тоже не регистрировать
    {
        //в заголовок страницы добавляем ссылку на .css файл
        HtmlLink cssLink = new HtmlLink();
        cssLink.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "ServControlEGridView.eresource.css.egv.css");
        cssLink.Attributes.Add("rel", "stylesheet");
        cssLink.Attributes.Add("type", "text/css");
        this.Page.Header.Controls.Add(cssLink);
        //Регистрация .js скриптов
        if (!this.DesignMode) // Test for ScriptManager and register if it exists
        {
            sm = ScriptManager.GetCurrent(Page); //получаем ссылку на ScriptManager, размещённый на странице
            if (sm == null) throw new HttpException("Контрол ScriptManager должен присутствовать на текущей странице/r/nThere must be a script manager on the page");
            sm.RegisterScriptControl(this);
        }
        //Устанавливаем флаг, что .js .css зарегистрированы. JsCssReg67 -произвольно придуманное строковое значение
        this.Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "JsCssReg67", string.Empty, false);
    }
    base.OnPreRender(e);
}

Теперь можно использовать jQuery JavaScript библиотеку.
Настроим "декорации" на клиентской стороне. На серверную сторону, в контексте перетаскивания столбца через postback будет передаваться текущий индекс перетаскиваемого столбца и индекс нового месторасположения столбца. Причём, если эти индексы совпадают, то на сервер ничего не передаётся -это если столбец начали перемещать и вернули обратно т.е. его местоположение не изменилось. Настройку "декораций" возложим на клиентскую сторону.
Функция $(document).ready(function() {…}); расположенная в каком либо подключённом .js файле запускается после загрузки всех DOM элементов страницы, в ней и огранизуем настройку всевозможных подсветок и некоторого функционального разнообразия в поведении разрабатываемого контрола.

Для настройки расширенной функциональности на стороне клиента через javascript, нужно получить ClientID текущего контрола. У контрола регистрируется javascript функция InitControl и в неё передаётся параметр ClientID. .js функция InitControl регистрируется в Render. Как раз в Render заканчивается регистрация всего, что связано с интерфейсом IScriptControl.
.cs файл:

protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
    if (!this.Page.ClientScript.IsClientScriptBlockRegistered("Regreg98")) //Чтобы несколько раз, на одной странице, одно и тоже не регистрировать
    {
        if (!this.DesignMode) sm.RegisterScriptDescriptors(this); //регистрация дескрипторов сценария, созданных методом GetScriptDescriptors
        this.Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "Regreg98", string.Empty, false); //Устанавливаем флаг. "Regreg98" произвольно выдуманный ключ
    }
    //Блок сценария, добавленный методом RegisterStartupScript выполняется после окончания загрузки страницы, но до вызова события OnLoad для страницы
    //Регистрируем/добавляем скрипт, собирающий уникальные идентификаторы экземпляров текущего контрола(на странице несколько контролов) в массив, который потом будет использован в $(document).ready(function(){}); для индивидуальной настройки каждого контрола
    //string key = this.ClientID потому что два разных скрипта с одним ключом проходят как один первый
    ScriptManager.RegisterStartupScript(this, this.GetType(), this.ClientID, "InitControl('" + this.ClientID + "');", true);
    …
    base.Render(writer);
}

Пока, единственное, что делает функция InitControl -это выводит на экран модальное окно с текстом ClientID инициализируемого в данный момент контрола.
.js файл:

function InitControl(controlid) 
{
    alert(controlid);
}

Блок сценария, добавленный методом RegisterStartupScript выполняется после окончания загрузки страницы, но до вызова события OnLoad для страницы. Не существует гарантии, что блоки сценария будут выводиться в порядке их регистрации.

Предполагалось 2 варианта инициализации расширенной функциональности контролов при загрузке/обновлении:

1.Основной задачей функции InitControl будет сбор ID всех контролов находящихся на странице в глобальный для текущей страницы массив. В последующем функция $(document).ready обрабатывает массив в цикле и настраивает все контролы текущего типа на странице.

2.InitControl не собирает ID всех контролов на странице, а настройка функциональности каждого контрола производится прямо в InitControl без использования $(document).ready. Первый способ кажется наиболее надёжным, т.к. прямо сказано, что $(document).ready гарантированно запустится после инициализации всех DOM элементов страницы. А вот будет ли загружен контрол на страницу перед вызовом InitControl прямого упоминания не нашол. Но судя по фразе "Блок сценария, добавленный методом RegisterStartupScript выполняется после окончания загрузки страницы, но до вызова события OnLoad для страницы", предположим, что всё будет хорошо. Реализуем второй вариант.

Чтобы удостовериться в последовательности вызовов тех или иных функций, используем точки останова BrakePoint. Рассмотрим, в какой последовательности вызываются функции контрола, при загрузке страници, содержащей 2 экземпляра разрабатываемого контрола-потомока GridView, с источником данных ObjectDataSource. Чтоб наглядней было, ниже скриншот из визуального редактора VS2008. Контролы расположены в UpdatePanel.90

Статического конструктора не будет. Описываются только функции контрола реализованые на текущий момент.

При запуске страницы в IE обозревателе

1.Конструктор public EGridView() первого контрола

2.Конструктор public EGridView() второго контрола

3.OnInit

4.OnInit

5.OnLoad текущего контрола, не страницы

6.OnLoad текущего контрола, не страницы

7.Страница со скрытыми во View MultiView 2мя экземплярами контролов:1

8.По выбору, через элемент меню начинает отображаться View с 2мя экземплярами контролов и снова:

9.Конструктор public EGridView() первого контрола

10.Конструктор public EGridView() второго контрола

11.OnInit

12.OnInit

13.OnLoad текущего контрола, не страницы

14.OnLoad текущего контрола, не страницы

15.OnRowDataBound 5 раз. Одна строка pager считается за 2.

16.OnPreRender

17.OnRowDataBound 7 раз (paging также и на top и 3 строки данных вместо двух)

18.OnPreRender

19.protected virtual IEnumerable<ScriptReference> GetScriptReferences()

20.Render

21.protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()

22.Render

23.public EGridView() -не могу предположить, почему опять был вызван конструктор первого контрола

24.public EGridView() - не могу предположить, почему опять был вызван конструктор второго контрола

25.Сообщение было сформировано .js function InitControl(controlid) { alert(controlid); }. Первого контрола.2

26.Такое же сообщение но для EGridView2. Иногда сообщения с веб-страницы появлялись на фоне отрисованных контролов таблиц.

27.redy -это .js: $(document).ready(function() { alert("ready"); }); Сообщение всегда появляется только после отрисовки страницы. К слову: На странице может быть сколько угодно событий $(document).ready. В этом случае функции выполняются в том порядке, в котором они были добавлены.3

Таким образом нужно в InitControl реализовать всю возможную часть инициализации текущего контрола.

Возник вопрос, как идентифицировать наличие строки, того или иного типа, отрисованной на странице. Строки paging верхняя нижняя, header, footer, строки данных. В VS C# можно индивидуально работать с каждым объектом. На странице вся таблица условно представлена одними строками и ячейками. Причём в визуальном редакторе VS, у контрола GridView, пэйджинг можно включить, можно выключить, можно включать/отключать заголовок и подвал. А на вэб странице строки не имеют признаков различия. Для различения типов строк их нужно как-то пометить. В общем решено передавать массив из 8ти ClientID строковых записей, в функцию .js инициализации конторла на стороне клиента, однозначно описывающий типы и наличие строк у таблицы. Идентификаторы строк данных не передаются. В render контрола добавляем:

//Создаём 8 параметров, передаваемых в .js. Table, PagerTop, PagerBottom, Header, Footer, не используется, не используется, номер конфигурационного столбца
string sartableid = "'"+this.ClientID+"',";
if (this.AllowPaging) { if ((this.PagerSettings.Position == PagerPosition.Top) | (this.PagerSettings.Position == PagerPosition.TopAndBottom)) sartableid += "'" + this.TopPagerRow.ClientID + "',"; else sartableid += "'',"; }else sartableid += "'',";
if (this.ShowHeader) sartableid += "'" + this.HeaderRow.ClientID + "',"; else sartableid += "'',";
if (this.ShowFooter) sartableid += "'" + this.FooterRow.ClientID + "',"; else sartableid += "'',";
if (this.AllowPaging) { if ((this.PagerSettings.Position == PagerPosition.Bottom) | (this.PagerSettings.Position == PagerPosition.TopAndBottom)) sartableid += "'" + this.BottomPagerRow.ClientID + "',"; else sartableid += "'',"; }else sartableid += "'',";
sartableid += "'" + "no" + "',"; //Флаг типа меню команд таблицы. Есть набор команд сервисных и набор пользовательских
sartableid += "'" + "no" + "',"; //Флаг визуализации, при перетаскивании, не только ячейки заголовка, но и всего столбца
sartableid += "'" + ConfigColumn.ToString() + "'"; //Номер столбца выбранного как конфигурационный
//Блок сценария, добавленный методом RegisterStartupScript выполняется после окончания загрузки страницы, но до вызова события OnLoad для страницы
//Регистрируем/добавляем скрипт, собирающий уникальные идентификаторы экземпляров текущего контрола(на странице несколько контролов) в массив, который потом будет использован в $(document).ready(function(){}); для индивидуальной настройки каждого контрола
//string key = this.ClientID потому что два разных скрипта с одним ключом проходят как один первый
//$$$            ScriptManager.RegisterStartupScript(this, this.GetType(), this.ClientID, "InitControl(" + sartableid + ");", true);
if (this.TopPagerRow!=null)
this.TopPagerRow.Attributes.Add("onmouseover", "InitControl2(" + sartableid + ");");

Обязательным к наличию является только первый/нулевой элемент -идентификатор самой таблицы. Если например другие элементы отсутствуют, то это будет означать отображение на странице только строк данных таблицы. Не разбирался почему, но строка pager идёт, как 2 строки -skp_tg+=2 и skp_lt-=2.
В .js аргументы передаваемые в функцию помещаем в массив, это будет выглядеть так:

function InitControl2() //
{
...
    var RowTypeArr = Array(); //Массив содержит ID строк таблицы table(id самой таблици, параметр обязателен к наличию), pager top, header, footer, pager bottom, если ="" значит строка такого типа отсутствует
    $.makeArray(arguments, RowTypeArr); //Создаём массив из аргументов переданных в функцию
    if (RowTypeArr.length != 8) return; //На всякий
...
}

Добавляем сюда же в InitControl чередование стилей строк данных таблицы:

//1. Чередование стилей строк данных контрола
    var skp_lt = $('#' + RowTypeArr[0] + " tr").size(); //общее количество строк. Строка pager идёт как 2 строки. Если есть pager top и bottom то это 4 строки
    var skp_tg = 0; //вычисляем количество строк к пропуску. Пропускаются строки пэйджера и заголвка при их наличии
    if (RowTypeArr[1] != "") skp_tg+=2; //наличие строки верхнего pager
    if (RowTypeArr[2] != "") skp_tg++; //наличие header
    if (RowTypeArr[3] != "") skp_lt--; //наличие footer
    if (RowTypeArr[4] != "") skp_lt-=2; //наличие строки нижнего pager
    skp_lt -= skp_tg; //Количество строк данных в таблице

    ConfigColumn = parseInt(RowTypeArr[7]); //номер конфигурационного столбца

    rows = $('#' + RowTypeArr[0] + " tr:gt(" + (skp_tg - 1) + "):lt(" + skp_lt + ")"); //Получаем все строки данных
    ColumnCount = $('#' + RowTypeArr[0]).find('th').length; //количество столбцов таблицы

        
    if (skp_tg > 0) { //Селекторы поиска по индексу :eq(), :lt(), :gt(), :even, :odd
        $('#' + RowTypeArr[0] + " tr:gt(" + (skp_tg - 1) + "):lt(" + skp_lt + "):even").addClass('OddDataRowStyle'); //Задаём стиль для всех чётных строк данных таблицы
        $('#' + RowTypeArr[0] + " tr:gt(" + (skp_tg - 1) + "):lt(" + skp_lt + "):odd").addClass('EvenDataRowStyle'); //Задаём стиль для всех не чётных строк данных таблицы
    } else { //для таблиц без строк заголовка и/или pager
        $('#' + RowTypeArr[0] + " tr:lt(" + skp_lt + "):even").addClass('OddDataRowStyle'); //Задаём стиль для всех чётных строк данных таблицы. Если отсутствует pager top и header
        $('#' + RowTypeArr[0] + " tr:lt(" + skp_lt + "):odd").addClass('EvenDataRowStyle'); //Задаём стиль для всех не чётных строк данных таблицы. Если отсутствует pager top и header
    }

Как видно, довольно заморочено получилось. Но так нужно, для разрешения всех возможных вариантов отображения штатной GrigView. Добавляем:

//2. Добавляем подсветку строк данных другим стилем MouseoverDataRowStyle, при наведении мыши
    if (skp_tg > 0) { $('#' + RowTypeArr[0] + " tr:gt(" + (skp_tg - 1) + "):lt(" + skp_lt + ")").hover(function() { $(this).addClass('MouseoverDataRowStyle'); }, function() { $(this).removeClass('MouseoverDataRowStyle'); }); } 
    else { $('#' + RowTypeArr[0] + " tr:lt(" + skp_lt + ")").hover(function() { $(this).addClass('MouseoverDataRowStyle'); }, function() { $(this).removeClass('MouseoverDataRowStyle'); }); }

Добавляем визуализацию перетаскивания столбцов и строк через draggable (jQuery UI). Если в результате перетаскивания [ячейки заголовка столбца]/[конфигурационной ячейки столбца] на новое место индекс изменился, то через postback сообщаем об этом серверу. Иначе ничего не происходит -это на случай, если пользователь начал перемещать ячейку, но потом вернул её на место. Перерисовку таблицы в браузере сначала производит клиент, потом сервер, согласно новому порядку столбцов. На стороне клиента перерисовка сделана для более удобной визуализации каждого перемещения столбца/строки. Конфигурационная ячейка столбца -любая ячейка столбца одного из столбцов таблицы условно принятого за конфигурационный. Этот столбец может быть задан произвольно из числа существующих столбцов, через соответствующий индекс. Инструкции, реализующие эту функциональность приведены ниже:

//6. Визуализация перетаскивания столбца через draggable
    if (RowTypeArr[2] != "") //Если заголовок существует
    {      
        $('#' + RowTypeArr[2]).addClass('HeaderStyle'); //Стиль заголовка
        $('#' + RowTypeArr[2] + " th").hover(function() { $(this).addClass('MouseoverHeaderCellStyle'); }, function() { $(this).removeClass('MouseoverHeaderCellStyle'); }); //Подсветка ячеек заголовка
        $('#' + RowTypeArr[2] + " th").draggable( //ячейки заголовка назначаем перемещаемыми
        {
        axis: 'x',              //Перемещение только по x
        //helper: 'clone',      //Элемент в процессе перетягивания до момента drop будет неподвижен, а под указателем мыши будет находиться его копия
        //helper: 'original',
        containment: 'parent',  //Перемещение только в пределах заголовка
        //cursor: 'e-resize',   //Вид курсора при перетаскивании
        delay: 400,             //Время в миллисекундах, по истечении которого, начнется перетаскивание
        distance: 5,            //Расстояние в пикселах, которое указатель мыши должен пройти после того, как вы нажали на кнопку мыши, прежде чем начнется перетаскивание
        //handle: 'p'           //Определяет, "ухватившись" за какой элемент внутри данного осуществляется перетаскивание. В текущем случае ухватив за абзац/текст.
        //revert: true,           //определяет, будет ли элемент возвращен на свое место после перетаскивания true - да
        //revertDuration: 0,   //определяет, за какое время элемент будет возвращен на свое место после перетаскивания
        //zIndex: 1,            //определяет значение свойства z-index (номер слоя) перемещаемого элемента
        opacity: 0.70,          //Устанавливает прозрачность перетаскиваемого элемента
        helper: function() //
        {            
            return $(this).clone().width($(this).width()).height($(this).height()); //Устанавливаем ширину и высоту helper равной ширине и высоте ячейки
        },        
        start: function(e, ui)  //Определяет функцию, код которой будет выполнен, когда перетаскивание начнется
        {
            SwapLeft = true;  //#Разрешон левосторонний обмен ячеек
            SwapRight = true; //#Разрешон правосторонний обмен ячеек
            OldColumnInd = $(this).parent().children().index($(this)); //Получаем индекс ячейки перемещаемого заголовка столбца
            //Если перемещаемым столбцом является конфигурационный, то очищаем его от возможности вертикально перемещать ячейки/строки
            //очищаем конфигурационный столбец от возможности вертикально перемещать ячейки/строки перед перемещение столбца
            rows.find("td:eq(" + ConfigColumn + ")").draggable('destroy');
            $(this).stop().animate({ opacity: '0.1' }, 200, "linear"); //Гасим видимость ячейки
        },
        stop: function(e, ui) //Определяет функцию, код которой будет выполнен, когда перетаскивание закончится
        {
            $(this).stop().animate({ opacity: '1' }, 500, "linear"); //Проявляем ячейку
            //Осуществляем postback на сервер. Передаём начальный и конечный индексы перемещённого столбца
            //.........            
            //Настраиваем конфигурационный столбец, запускаем InitControl2() для перенастройки возможно нового столбца
            RowsDraggInit(ThisTable, RowTypeArr, skp_lt, skp_tg);
        },
        drag: function(e, ui) //Определяет функцию, код которой будет выполнен, когда указатель мыши сдвинется во время перетаскивания элемента
        {
            //Производим обмен соседних ячеек, если перемещаемая ячейка на 50% перекрыла соседнюю
            //Проверка смены направления движения helper. Своеобразный гистерезис, доя предотвращения дрожания столбцов
            var hl_new = ui.helper.offset().left;  //новые координаты left helper/а                        //#
            if (hl_new > hl_old) { SwapLeft = false; SwapRight = true; } //# смещение вправо
            if (hl_new < hl_old) { SwapLeft = true; SwapRight = false; } //# смещение влево
            var Curind = $(this).parent().children().index($(this)); //Получаем индекс ячейки перемещаемого заголовка столбца
            //Левостороннее смещение
            if ((Curind != 0) & (SwapLeft == true)) //Запрет смещения влево перед первым столбцом
            {
                SwapRight = false;                //#Запрещён правосторонний обмен ячеек заголовка
                hl_old = ui.helper.offset().left; //#
                if (($(this).prev().offset().left + $(this).prev().width() - ui.offset.left) > ((Math.min($(this).width(), $(this).prev().width())) * 0.5)) //
                {
                    $(this).insertBefore($(this).prev()); //Левостороннее смещение ячейки
                    //Проверяем, нужно ли обменять столбцы данных и сообщать о смене порядка столбцов серверу
                    rows.each(function() //Перебор по всем строкам данных
                    {
                        if (Curind == ConfigColumn + 1) $(this).find("td:eq(" + ConfigColumn + ")").removeClass('ConfigColumnStyle');
                        if (Curind == ConfigColumn)     $(this).find("td:eq(" + ConfigColumn + ")").removeClass('ConfigColumnStyle');
                        $(this).find("td:eq(" + Curind + ")").insertBefore($(this).find("td:eq(" + (Curind - 1) + ")"));
                        if (Curind == ConfigColumn + 1) $(this).find("td:eq(" + ConfigColumn + ")").addClass('ConfigColumnStyle');
                        if (Curind == ConfigColumn)     $(this).find("td:eq(" + ConfigColumn + ")").addClass('ConfigColumnStyle');
                    });
                }
            } else hl_old = ui.helper.offset().left;
            //Правостороннее смещение            
            if ((Curind != (ColumnCount - 1)) & (SwapRight == true)) //Запрет смещения вправо последнего столбца
            {
                SwapLeft = false;                 //#Запрещён левосоронный обмен ячеек заголовка
                hl_old = ui.helper.offset().left; //#
                if ((ui.offset.left + $(this).width() - $(this).next().offset().left) > ((Math.min($(this).width(), $(this).next().width())) * 0.5)) //
                {
                    $(this).insertAfter($(this).next()); //Правостороннее смещение ячейки
                    rows.each(function() //Перебор по всем строкам данных
                    {
                        if (Curind == ConfigColumn - 1) $(this).find("td:eq(" + ConfigColumn + ")").removeClass('ConfigColumnStyle');
                        if (Curind == ConfigColumn)     $(this).find("td:eq(" + ConfigColumn + ")").removeClass('ConfigColumnStyle');
                        $(this).find("td:eq(" + Curind + ")").insertAfter($(this).find("td:eq(" + (Curind + 1) + ")"));
                        if (Curind == ConfigColumn - 1) $(this).find("td:eq(" + ConfigColumn + ")").addClass('ConfigColumnStyle');
                        if (Curind == ConfigColumn)     $(this).find("td:eq(" + ConfigColumn + ")").addClass('ConfigColumnStyle');
                    });
                }
            } else hl_old = ui.helper.offset().left;
        }
    });
    }
    //7. Визуализация перетаскивания строк таблицы, реализованна также, как и перетаскивание столбцов
    rows.find("td:eq(" + ConfigColumn + ")").addClass('ConfigColumnStyle'); //Настраиваем стиль конфигурационного столбца
    RowsDraggInit(ThisTable, RowTypeArr, skp_lt, skp_tg);    
}
//функция инициализации конфигурационного столбца с номером ConfigColumn
//Неявно использует: rows, ConfigColumn, SwapTop, SwapBottom, OldRowInd, hl_old
function RowsDraggInit(ThisTable, RowTypeArr, skp_lt, skp_tg) //
{
    //Границы перемещения helper/а в пределах столбца данных
    var x_left = ThisTable.offset().left;
    var y_left = rows.first().offset().top;
    var x_right = x_left + ThisTable.width();
    var y_right = rows.last().offset().top;
    rows.find("td:eq(" + ConfigColumn + ")").draggable( //ячейки столбца данных назначаем перемещаемыми
    {
    axis: 'y',              //Перемещение только по y
    //helper: 'clone',      //Элемент в процессе перетягивания до момента drop будет неподвижен, а под указателем мыши будет находиться его копия
    //helper: 'original',
    //containment: 'parent',  //Перемещение только в пределах столбца
    delay: 200,             //Время в миллисекундах, по истечении которого, начнется перетаскивание
    distance: 5,            //Расстояние в пикселах, которое указатель мыши должен пройти после того, как вы нажали на кнопку мыши, прежде чем начнется перетаскивание
    opacity: 0.70,          //Устанавливает прозрачность перетаскиваемого элемента
    //containment: FrCellClone, //$('div#ccdiv'),
    //containment: rows.first().parent(), //Область перемещения ограничена текущей таблицей
    containment: [x_left, y_left, x_right, y_right], //Перемещение helper в указанной области
    //stack: "td:eq(" + ConfigColumn + ")", //Перемещаемый элемент автоматически выводим на передний план
    helper: function() { return $(this).clone().width($(this).width() + 1).height($(this).height()); }, //Устанавливаем ширину и высоту helper равной ширине и высоте ячейки
    start: function(e, ui)  //Определяет функцию, код которой будет выполнен, когда перетаскивание начнется
    {
        SwapTop = true;  //#Разрешон верхний обмен ячеек
        SwapBottom = true; //#Разрешон нижний обмен ячеек
        OldRowInd = $(this).parent().index(); //Получаем индекс строки данных таблицы
        if (RowTypeArr[1] != "") OldRowInd -= (skp_tg - 1); //строка pager и header. Если есть pager, который учтен, как 2 строки
        else OldRowInd -= skp_tg; //строка header
        $(this).stop().animate({ opacity: '0.1' }, 200, "linear"); //Гасим видимость ячейки
    },
    stop: function(e, ui) //Определяет функцию, код которой будет выполнен, когда перетаскивание закончится
    {
        rows = $('#' + RowTypeArr[0] + " tr:gt(" + (skp_tg - 1) + "):lt(" + skp_lt + ")"); //Получаем новую последовательность строк данных
        $(this).stop().animate({ opacity: '1' }, 500, "linear"); //Проявляем ячейку
        //Возвращаем раскраску области данных таблицы
        rows.removeClass('OddDataRowStyle'); rows.removeClass('EvenDataRowStyle');
        if (skp_tg > 0) { //Селекторы поиска по индексу :eq(), :lt(), :gt(), :even, :odd
            $('#' + RowTypeArr[0] + " tr:gt(" + (skp_tg - 1) + "):lt(" + skp_lt + "):even").addClass('OddDataRowStyle'); //Задаём стиль для всех чётных строк данных таблицы
            $('#' + RowTypeArr[0] + " tr:gt(" + (skp_tg - 1) + "):lt(" + skp_lt + "):odd").addClass('EvenDataRowStyle'); //Задаём стиль для всех не чётных строк данных таблицы
        } else { //для таблиц без строк заголовка и/или pager
            $('#' + RowTypeArr[0] + " tr:lt(" + skp_lt + "):even").addClass('OddDataRowStyle'); //Задаём стиль для всех чётных строк данных таблицы. Если отсутствует pager top и header
            $('#' + RowTypeArr[0] + " tr:lt(" + skp_lt + "):odd").addClass('EvenDataRowStyle'); //Задаём стиль для всех не чётных строк данных таблицы. Если отсутствует pager top и header
        }
        //Осуществляем postback на сервер. Передаём начальный и конечный индексы перемещённой строки
        //.........
    },
    drag: function(e, ui) //Определяет функцию, код которой будет выполнен, когда указатель мыши сдвинется во время перетаскивания элемента
    {
        ui.helper.offset({ left: $(this).offset().left }); //код, совместно с axis: 'y', разрешает проблему вертикального перемещения ячейки, в пределах столбца
        //Производим обмен соседних строк, если перемещаемая строка на 50% перекрыла соседнюю
        //Проверка смены направления движения helper. Своеобразный гистерезис, доя предотвращения дрожания столбцов
        var hl_new = ui.helper.offset().top;  //новые координаты helper/а
        if (hl_new > hl_old) { SwapTop = false; SwapBottom = true; } //# смещение вниз
        if (hl_new < hl_old) { SwapTop = true; SwapBottom = false; } //# смещение вверх
        var Curind = $(this).parent().index(); //Получаем индекс перемещаемой строки
        //Смещение вверх //$(this) -здесь это указатель на ячейку перемещаемой строки
        if ((Curind != 0) & (SwapTop == true)) //Запрет смещения вверх нулевой строки
        {
            SwapBottom = false;               //#Запрещён правосторонний обмен ячеек заголовка
            hl_old = ui.helper.offset().top;  //#
            if (($(this).offset().top - ui.offset.top) > ((Math.min($(this).height(), $(this).parent().prev().height())) * 0.5)) //
            {
                $(this).parent().insertBefore($(this).parent().prev()); //Левостороннее смещение ячейки
            }
        } else hl_old = ui.helper.offset().top;
        //Смещение вниз
        if ((Curind != rows.last().index()) & (SwapBottom == true)) //Запрет смещения вправо последнего столбца
        {
            SwapTop = false;                 //#Запрещён левосоронный обмен ячеек заголовка
            hl_old = ui.helper.offset().top; //#
            if ((ui.offset.top - $(this).next().offset().top) > ((Math.min($(this).height(), $(this).parent().next().height())) * 0.5)) //если перекрытие строк по вертикали более 0.5
            {
                $(this).parent().insertAfter($(this).parent().next()); //Нижнее смещение ячейки
            }
        } else hl_old = ui.helper.offset().top;
    }
    });
}

Для расширения возможностей управления элементами веб страницы можно использовать клиентский элемент управления. Клиентский элемент управления можно связать с серверным элементом управления с помощью реализации интерфейса IScriptControl в серверном элементе управления.
Клиентский класс, объявляемый в .js файле -в каком-то смысле аналог привычного C класса. У него есть конструктор свойства методы. В GetScriptDescriptors() функции интерфейса IScriptControl создаётся экземпляр клиентского класса "ScriptControlDescriptor descriptor = new ScriptControlDescriptor("ServControlEGridView.EGridView", this.ClientID);" и задаются свойства, которые будут переданны в клиентский класс.

Живой пример реализованной таблицы можно посмотреть здесь: https://anteh.com/EGridViewDemo.aspx Пример отключён8

Пример демонстрирует таблицу-потомок GridView с возможностью перемещения столбцов и строк
Для удобства отладки, инициализация расширенных свойств контрола таблицы запускается при onmouseover событии над верхним пейджером. Т.е. в текущем случае верхний пейджер должен обязательно присутствовать у таблицы. Рядом с указателем мыши 2 поля в верхнем текущие координаты курсора, в нижнем количество запусков .js функции инициализирующей расширенные возможности контрола или количества onmouseover над верхним пейджером.
Перетаскивание строк можно производить только через вертикальное перетаскивание ячеек конфигурационного столбца. Конфигурационный столбец выделен светло оранжевым.
Таблица содержит столбцы сформированные как штатными средствами в визуальном редакторе, так и столбцы добавленные через ObjectDataSource.

Если нужен весь .js код реализующий перетаскивание столбцов и строк, то его можно посмотреть в соответствующем файле во временной папке браузера. .js код был подвергнут минификации, но его обфускация не производилась и при необходимости можно развернуть код через antcomm.exe утилиту.


Copyright ©Новиков Алексей Александрович,

2012-2017 Санкт-Петербург, 197372, ООО "Антех ПСБ",

anteh собака bk.ru