September 16, 2011

Hello, Mono


    В универе надо делать лабы на C#, а сидеть под виндой совсем не хочется. Да и Visual Studio мне очень не нравится. Даже не смотря на то, что она у меня бесплатная, как для студента. Пришлось разбираться с Mono. Возни получилось больше не с Mono, а с gtk-sharp.

     Установка
    Поскольку под рукой у меня есть Ubuntu и Windows XP, то рассмотрим процесс установки всякого барахла именно для них (несмотря на то, что раздел скачивания на официальном сайте, а так же гугл никто не отменял).

   Установка для Ubuntu
    Установить нам надо три вещи:
  1. Непосредственно Mono
  2. Monodevelop
  3. Gtk-sharp для GUI
    Основных вариантов установки тут у нас целых три. Лично мне больше нравится 1-й.

    Вариант 1. Использование командной строки.
    Для установки вещей из пунктов 1 и 2, выполняем:
sudo apt-get remove mono-gmcs mono-gac mono-utils monodevelop monodoc-browser monodevelop-nunit monodevelop-versioncontrol mono-xsp
    Gtk-sharp можно установить так:
sudo apt-get install gtk-sharp2
    Вариант 2. Использование Ubuntu Software Center.
    В поиск вводим "monodevelop", устанавливаем и получаем почти такой же букет, как в 1-м варианте, но без gtk-sharp.  Для того, чтобы это исправить в поиск вводим "gtk sharp" и скачиваем "GTK# 2.10 suite".
    
    Вариант 3. Собери сам, "будь мужиком".
    Поповоду исходников самого Mono для Ubuntu здесь я толком ничего не нашел. Только упоминание про Synaptic. Исходники Monodevelop здесь, а gtk-sharp здесь, внизу справа.

    Установка для Windows
    Ставить Mono под Windows при существовании Visual Studio, наверное, является мазохизмом. Скачать его можно здесь, но, вероятно, максимум что понадобится, так это "Gtk# for .NET". Вот его-то и можно скачать для того, чтобы приложения с GUI, разработанными с помощью Gtk-sharp, запускались под виндой (при наличии .NET, конечно).

    Первый проект
    В качастве первого проекта будет приложение с Gtk-GUI, позволяющее выполнять простейшие операции со списком граждан, а именно добавление и удаление. Пример работы приложения на рисунке ниже.


    Компоненты, примененные в GUI:
  1. HPaned
  2. VBox
  3. Label
  4. Entry
  5. Button
  6. TreeView
  7. Dialog
  8. Menu
   Данные о гражданах хранятся в объектах класса Citizen, помещенных в System.Collections.Generic.LinkedList<Citizen>. Добавление происходит по нажатию на кнопку "Add", а удаление - по выбору пункта "Delete" в выпадающем меню. Класс Citizen:
namespace lab_01{

    public class Citizen{

        public string name;
        public string passportCode;
        public string nationality;
        
        public Citizen (
                string name,
                string passportCode,
                string nationality){
            this.name = name;
            this.passportCode = passportCode;
            this.nationality  = nationality;
        }
    }
}
   Все исходники проекта размещаются внизу сообщения, а здесь рассмотрим только основные мометны создания GUI с помощью Gtk-sharp, примененные в проекте.

     PopupMenu
    Эта штука доставила больше всего проблем. Для того, чтобы прикрутить её к TreeView пришлось потратить почти два вечера. Это правда. Я сам в шоке.
   Дело в том, что у объектов, классы которых наслебуются от Gtk.Widget есть сигнал "PopupMenu". Казалось бы, что в нем кроется решение. Но это не так, что и сбивает с толку. Этот сигнал в конечном итоге оказался вообще не при чем. Его нужно использовать, если вызывать контекстное меню по сочетанию клавиш, например, "Shift+F10". Для событий от мыши есть сигналы "ButtonPressEvent" и "ButtonReleaseEvent". Т.к. первый работать не хотел, то пришлось использовать второй.
    Добавить сигнал можно 2-мя способами:
    1. При редактировании GUI в свойствах объекта во вкладке "Signals" выбрать необходимый сигнал, указать имя обработчика событий и 2 раза нажать на название сигнала. После этого в файле класса, где описан объект появится запись наподобие этой:
this.citizensView.ButtonReleaseEvent += 
    new Gtk.ButtonReleaseEventHandler(
        this.CitizensViewButtonReleaseEventHandler);
    Сам файл класса, например "MainWindow.cs",  будет размещаться в подкаталоге проекта "gtk-gui".
     2. Сигнал так же можно добавить вручную, в каком-нибудь методе инициализации или конструкторе, записав практически то же самое, что и в строке выше ("практически", потому, что зависит от того, в каком классе строка будет размещаться).
  Здесь у нас citizensView - это Gtk.TreeView, а CitizensViewButtonReleaseEventHandler - название метода-обработчика сигнала:
    private static int MOUSE_RIGHT_BUTTON_CODE = 3;
    private PopupMenu citizensViewPopupMenu;
    
    ................

    protected virtual void CitizensViewButtonReleaseEventHandler (
            object o, Gtk.ButtonReleaseEventArgs args){
        if (RightMouseWasClicked(args.Event)){    
            citizensViewPopupMenu.doPopup();
        }
    }

    protected bool RightMouseWasClicked(Gdk.EventButton evt){
        return evt.Button==MOUSE_RIGHT_BUTTON_CODE;
    }
    MOUSE_RIGHT_BUTTON_CODE - код правой кнопки мыши.
    Класс PopupMenu:
using Gtk;
using System;

namespace lab_01{

    public class PopupMenu{

        Menu popupMenu = new Menu();
        
        public PopupMenu (){
        }
        
        public void addItemByTitleWithHandler(
                string itemTitle, EventHandler handler){
            MenuItem item = new MenuItem(itemTitle);
            item.Activated += handler;
            item.Show();
        
            popupMenu.Append(item);
        }
        
        public void doPopup(){
            popupMenu.Popup(
                null,null,null,3,Gtk.Global.CurrentEventTime);
        }
    }
}
    Инициализация PopupMenu:
    private static string TITLE_DELETE = "Delete";

    ................


    protected void CitizensViewPopupInit(){
        citizensViewPopupMenu = new PopupMenu();
        EventHandler deleteHandler = 
            new EventHandler(CitizensViewDeleteEventHandler);
        citizensViewPopupMenu.addItemByTitleWithHandler(
            TITLE_DELETE, deleteHandler);
    }

    ................

    protected void CitizensViewDeleteEventHandler(
            object sender, EventArgs e){
        DeleteSelectedCitizen();
    }
    TreeView 
   Подробное использование этого класса описано здесь. Остановимся только на основных моментах. 
     1. Инициализация.
  За инициализацию CitizensView в проекте отвечает класс "CitizensViewInitializer". В нем создается модель для TreeView, добавляются колонки, заголовки для них, а так же происходит связывание колонок с аттрибутами класа "Citizen". Рассмотрим код создания модели и инициализации колонки "Name":
    private static string COLUMN_NAME_TITLE = "Name";
    
    ................

    public void doInit(){
            CitizensViewModelInit();
            CitizensViewColumnsInit();
    }
        
    private void CitizensViewModelInit(){
        Gtk.ListStore listStoreModel = 
            new Gtk.ListStore (typeof (Citizen));
        citizensView.Model = listStoreModel;
    }
        
    private void CitizensViewColumnsInit(){
        CitizensViewColumnNameInit();
        CitizensViewColumnPassportCodeInit();
        CitizensViewColumnNationalityInit();
    }
    
    private void CitizensViewColumnNameInit(){
        Gtk.TreeViewColumn nameColumn = new Gtk.TreeViewColumn ();
        Gtk.CellRendererText nameCell = new Gtk.CellRendererText ();
        
        nameColumn.Title = COLUMN_NAME_TITLE;
            
        nameColumn.PackStart (nameCell, true);
        nameColumn.SetCellDataFunc (
            nameCell, new Gtk.TreeCellDataFunc (RenderCitizenName));
            
        citizensView.AppendColumn (nameColumn);
    }
        
    private void RenderCitizenName (
            Gtk.TreeViewColumn column,
            Gtk.CellRenderer cell,
            Gtk.TreeModel model,
            Gtk.TreeIter iter){
        Citizen citizen = (Citizen) model.GetValue (iter, 0);
        (cell as Gtk.CellRendererText).Text = citizen.name;
    }
    
    ................
     Отображение содержимого LinkedList
    protected void RefreshCitizensList(){
        Gtk.ListStore model = (Gtk.ListStore)citizensView.Model;
        model.Clear();
        ShowUpCitizensList(model);
    }
    
    protected void ShowUpCitizensList(Gtk.ListStore model){
        foreach (Citizen citizen in citizensList){
            model.AppendValues(citizen);
        }
    }
    Получение объекта по выбранной строке в TreeView
    Получение объекта по выбранной строке продемонстрировано в методе DeleteSelectedCitizen:
    private static string MESSAGE_SELECT_ITEM =
        "Select something to delete, please!";

    .................

    protected void DeleteSelectedCitizen(){
        TreeSelection selection = citizensView.Selection;
        Gtk.TreeIter iter;
        Gtk.TreeModel model;
        selection.GetSelected(out model, out iter);
            
        if (NothingIsSelected(iter)){
            MessageUI.ShowMessage(MESSAGE_SELECT_ITEM, this);
        } else {
            Citizen citizen = (Citizen)model.GetValue(iter, 0);        
            citizensList.Remove(citizen);
            RefreshCitizensList();
        }
    }
    
    protected bool NothingIsSelected(Gtk.TreeIter iter){
        return iter.Stamp==0;
    }
    Всплывающее сообщение (Notification)
    Метод "ShowMessage" я честно взял где-то после гугления. Добавил всего лишь параметр "Gtk.Window window". Вот описание метода:
public static void ShowMessage(string p, Gtk.Window window){
    Gtk.Dialog dlg =
        new Dialog("Message", window, DialogFlags.Modal);
    dlg.AddButton(Gtk.Stock.Ok, Gtk.ResponseType.Ok);
    dlg.DefaultResponse = Gtk.ResponseType.Ok;
        
    ScrolledWindow sw = new ScrolledWindow();
        
    Label l = new Label(p);
    l.WidthRequest = 285;
    l.Wrap = true;
            
    sw.AddWithViewport(l);
    
    dlg.VBox.PackStart(sw);
    dlg.SetDefaultSize(320, 160);
    
    dlg.ShowAll();
    dlg.Run();
    dlg.Destroy();
}
    Пример его работы:


Пример работы приложения под Windows:




No comments:

Post a Comment