Рано или поздно в своей работе программист доходит до той точки, когда писать программы “на коленке” уже не имеет смысла, т.к. время – конечный ресурс и с каждым годом становится все более и более ценным. В таком случае полезным бывает использование уже существующих наработок как, непосредственно, для реализации того или иного функционала, так и для разработки архитектуры ПО.

Удачным и довольно эффективным способом сократить время, необходимое на разработку архитектуры, является использование шаблонов проектирования (design patterns).

Одним из довольно популярных в наши дни шаблонов проектирования является Model-View-Controller (MVC, Модель-представление-контроллер). Об использовании этого шаблона проектирования, в применении для разработки мобильных приложений на .NET Compact Framework, я расскажу в этот раз.

Итак, что нам дает MVC в общем? MVC позволяет разделить функционал приложения на три части:

  • Model (Модель) – содержит только данные и “ничего больше не делает”. На самом деле фраза в кавычках значит, что Model предоставляет API для получения данных, и API для реакции на запросы об изменении данных (обычно такие запросы приходят от контроллера), но на остальной функционал приложения или отдельного модуля, а также на функционал по отображению данных, модель не оказывает никакого влияния и даже не знает о них.
  • Controller (Контроллер) – обрабатывает данные, введенные пользователем, и уведомляет Model и Controller о необходимости реакции на действия пользователя. Модель, в этом случае, может изменять состояние хранимых ею данных, а представление должно обеспечить отображение актуальных данных.
  • View (представление) – отвечает за отображение актуальных данных, предоставляемых моделью. View – это то, что мы видим на экране, пользовательский интерфейс. При использовании шаблона MVC пользовательский интерфейс не может и не должен напрямую изменять данные. Он должен уведомлять контроллер о действии пользователя, а контроллер, если это необходимо, будет запрашивать изменение данных в модели.

Использование MVC позволяет уменьшить зависимости между компонентами системы таким образом, что изменение одного компонента приводит к минимальным изменениям в остальных.

Но довольно теории. Перейдем к рассмотрению примера использования MVC для разработки приложений на .NET Compact Framework. Оказывается, что все велосипеды уже изобретены до нас, и для мобильных приложений уже существует реализация паттерна MVC – Mobile MVC Framework, который, вместе с исходным кодом, доступен на CodePlex.

Здесь и далее все примеры будут использовать именно эту библиотеку для реализации MVC.

Итак, давайте начнем знакомство.

Первое, что необходимо сделать, это создать приложение на .NET Compact Framework. Для этого в Microsoft Visual Studio выбираем меню File -> New -> Project и создаем Smart Device Project на C#.

Создание приложения на .NET Compact Framework

Указав название проекта, жмем OK. После этого необходимо выбрать тип приложения. Нам подходит “Device Application

Выбор типа приложения для .NET Compact Framework

После того как наш проект создан, распаковываем исходный код Mobile MVC Framework в папку с решением.

Распаковываем исходный код Mobile MVC Framework в папку с решением

Добавляем проект библиотеки Mobile MVC Framework в решение.

Добавляем Mobile MVC Framework в решение

Для нашего приложения добавляем ссылку на проект библиотеки в References.

Добавляем Mobile MVC Framework в References для приложения

Отлично. Мы получили пустое приложение с одной формой. Теперь займемся внесением изменений.

Создаем новую форму. Это будет форма ввода имени пользователя и пароля при старте приложения.

Создаем новую форму в проекте приложения

На форму добавляем два компонента TextBox (для ввода имени пользователя и пароля), два компонента Label и в главное меню формы добавляем пункты OK и Exit.

Форма логина

Теперь выполним изменения в исходном коде формы.

Формы в приложении, использующем Mobile MVC Framework должны быть отнаследованы от класса ViewForm. Для этого меняем у нашей формы имя родительского класса.

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Mobile.Mvc;

namespace MobileMVCTest
{
    public partial class LoginForm : ViewForm
    {
        public LoginForm()
        {
            InitializeComponent();
        }
    }
}

По нажатию на кнопку OK наша форма будет отправлять уведомление контроллеру. Для того чтобы отправить уведомление о необходимости проверки логина и пароля, нам необходимо сохранить состояние формы. Состояние компонента сохраняется в переменной ViewData. Для уведомления контроллера используется метод OnViewStatChanged(). Каждое действие пользователя должно иметь уникальное имя, которое будет передано в качестве параметра в метод OnViewStatChanged(). Благодаря уникальному имени контроллер сможет идентифицировать действие пользователя и обработать его соответствующим образом. Уведомления от контроллера обрабатываются в методе OnUpdateView(). Все уведомления, приходящие от контроллера также будут иметь уникальные названия, с помощью которых форма сможет их идентифицировать и обработать.

Полный исходный код формы предствален ниже:

namespace MobileMVCTest
{
    public partial class LoginForm : ViewForm
    {
        public LoginForm()
        {
            InitializeComponent();
        }

        private void mnuOK_Click(object sender, EventArgs e)
        {
            ViewData["Login"] = txtLogin.Text;
            ViewData["Password"] = txtPassword.Text;
            OnViewStateChanged("LoginAction");
        }

        private void mnuExit_Click(object sender, EventArgs e)
        {
            OnViewStateChanged("ExitAction");
        }

        protected override void OnUpdateView(string key)
        {
            if(key == "LoginSuccess")
            {
                DialogResult = DialogResult.OK;
                Close();
            }
            else if(key == "LoginError")
            {
                txtLogin.Text = string.Empty;
                txtPassword.Text = string.Empty;
                MessageBox.Show("Error");
            }
        }
    }
}

Редактирование формы мы закончили. Теперь нужно создать класс контроллера.

Создаем контроллер для формы логина

Основной код в классе контроллера сосредоточен в методе OnViewStateChanged(), который вызывается из главной формы. Если приходит уведомление о том, что пользователь нажал кнопку “OK”, то выполняется проверка введенных данных. При соответствии данных форме отсылается уведомление “LoginSuccess”, в противном случае уведомление “LoginError”.

Если была нажата кнопка “Exit” (и в контроллер поступило уведомление “ExitAction”), то программа завершается.

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Mobile.Mvc;
using System.Windows.Forms;

namespace MobileMVCTest
{
    class LoginController : Controller
    {
        public LoginController(IView view)
            : base(view)
        {
        }

        protected override void OnViewStateChanged(string key)
        {
            if(key == "LoginAction")
            {
                if( (View.ViewData["Login"].ToString() == "TestLogin") &&
                    (View.ViewData["Password"].ToString() == "TestPassword"))
                {
                    View.UpdateView("LoginSuccess");
                }
                else
                {
                    View.UpdateView("LoginError");
                }
            }
            if(key == "ExitAction")
            {
                Application.Exit();
            }
        }
    }
}

Все идеально и просто. Форма не знает, каким образом обрабатываются введенные данные. Она всего лишь сохраняет свое состояние и уведомляет об этом контроллер. Контроллер также ничего не знает о том, какие есть контролы на форме. Он также сохраняет состояние и уведомляет форму.

Теперь нам осталось всего лишь внести небольшие изменения в класс приложения:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows.Forms;

namespace MobileMVCTest
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [MTAThread]
        static void Main()
        {
            LoginForm loginForm = new LoginForm();
            LoginController controller = new LoginController(loginForm);
            if(loginForm.ShowDialog() == DialogResult.OK)
            {
                Application.Run(new MainForm());
            }
        }
    }
}

Здесь мы создали форму и контроллер для нее. При успешном завершении работы формы (если данные об учетной записи пользователя были введены корректно) приложение продолжает свою работу. В противном случае приложение завершается.

Продолжение следует…

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

This post has 3 Comments

3
  1. Спасибо, интересно. Хочу попробовать. Жду продолжения)

  2. откуда код в форме LoginForm знает метод контроллера OnViewStateChanged кторый еще не создан?

Leave a Reply

Your email address will not be published. Required fields are marked *

Р.

Работаем с Flickr на .NET Compact Framework

Что-то все больше меня уносит в сторону ПО, работающего со всякими online-сервисами. Вон было недавно о Google Translate и Yahoo! Maps, а сегодня буду рассказывать о том как работать с сервисом хостинга фотографий Flickr на .NET Compact Framework.

Итак, предметом нашего внимания сегодня будет библиотека Flickr.NET, которую можно найти на CodePlex. Flickr.NET – это библиотека с открытым исходным кодом, есть версия для .NET Compact Framework. Вот ее мы и будем использовать.

Для начала Создаем новый проект для Smart Device на C#.

Затем распаковываем исходный код Flickr.NET в папку с созданным решением (рядом с папкой, в которой находится исходный код только что созданного проекта).

После этого добавляем проект FlickrNetCF в решение

Добавляем ссылку на проект FlickrNetCF в список зависимостей нашего приложения (с помощью Project -> Add Reference).

Ну вот, теперь можно работать.

Рисуем вот такую форму:

На форме:

  • Поле ввода запроса для поиска изображений
  • Кнопка поиска
  • Список (ListView) для просмотра информации о найденных изображениях
  • Кнопка открытия формы для просмотра полноразмерного изображения

Теперь можно заняться, собсьвенно, работой с Flickr. Для работы с сервисом в библиотеке FlickrNetCF есть класс Flickr. Для работы ему необходимы:

  • Application Key
  • Secret Key

Получить эти два ключа можно вот по этому адресу.

Создаем объект для работы с сервисом

using FlickrNet;
...
Flickr _flickr = new Flickr();
_flickr.ApiKey = "<здесь будет ваш API Key>";
_flickr.ApiSecret = "<здесь будет ваш Secret Key>";

Теперь нам неплохо бы обеспечить поиск изображений и отображение превью, а также информации об изображении. Загрузка превью – процесс длительный, поэтому обрабатывать информацию, полученную от сервиса будем в отдельном потоке:

Thread _downloadThread;
string _searchText = "";
...
private void StartDownloadingPreviws()
    {
    if (_downloadThread != null)
    {
        _downloadThread.Abort();
    }
    photoInfoListView.Items.Clear();
    photoThumbnailList.Images.Clear();
    _searchText = searchTextBox.Text;
    _downloadThread = new Thread(new ThreadStart(DownloadPhotosDelegate));
    _downloadThread.Start();
}

Для поиска изображений в классе Flickr предусмотрен метод PhotosSearchText(), который в качестве параметра принимает строку поискового запроса. Именно этот метод мы и будем использовать для получения информации о найденных фотографиях. Метод PhotosSearchText() возвращает объект класса Photos, из которого мы можем получить всю информацию о найденных изображениях (свойство PhotoCollection класса Photos возвращает массив объектов Photo, каждый из которых содержит полную информацию об одном изображении, например, название, ссылку на превью, ссылку на полноразмерное изображение и др.)

void DownloadPhotosDelegate()
{
    try
    {
        Photos _photos = _flickr.PhotosSearchText(_searchText);
        foreach (Photo photo in _photos.PhotoCollection)
        {
            MemoryStream photoStream = new MemoryStream();
            WebRequest request = HttpWebRequest.Create(photo.SquareThumbnailUrl);
            WebResponse response = request.GetResponse();
            MemoryStream stream = new MemoryStream();
            Stream responseStream = response.GetResponseStream();
            int readCount = 0;
            byte[] buffer = new byte[1024];
            while ((readCount =
                responseStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                stream.Write(buffer, 0, readCount);
            }
            response.Close();

            Bitmap bmp = new Bitmap(stream);
            string originalURL = photo.LargeUrl;
            string title = photo.Title;
            Invoke(new AddItemDelegate(AddItemToList),
                new object[] { bmp, title,  originalURL})
        }
    }
    catch (ThreadAbortException)
    {
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Т.к. в .NET неглавный поток не может работать с элементами GUI, то добавление превью изображений в список нам необходимо делать в отдельном методе, который будет вызываться посредством Invoke():

Invoke(new AddItemDelegate(AddItemToList), new object[] { bmp, title,  originalURL});
...
private delegate void AddItemDelegate(Bitmap bmp, string name, string originalURL);

Bitmap ResizeBitmap(Bitmap b, System.Drawing.Size size)
{
    Bitmap result = new Bitmap(size.Width, size.Height);
    using (Graphics g = Graphics.FromImage((Image)result))
        g.DrawImage(b, 
            new Rectangle(0, 0, result.Width, result.Height), 
            new Rectangle(0, 0, b.Width, b.Height), 
            GraphicsUnit.Pixel);
    return result;
}

void AddItemToList(Bitmap bmp, string name, string originalURL)
{
    try
    {
        photoThumbnailList.Images.Add(bmp);
        ListViewItem newItem = new ListViewItem(name);
        newItem.ImageIndex = photoThumbnailList.Images.Count - 1;
        newItem.Tag = originalURL;
        photoInfoListView.Items.Add(newItem);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

ОК, с главной формой пока закончили. теперь приступим к созданию формы для просмотра полноразмерного изображения.
Добавляем в проект новую форму. На нее кладем Panel (Dock = Fill, AutoScroll = true), на Panel кладем PictureBox.
Теперь нам нужно обеспечить загрузку изображения в отдельном потоке. Подход приблизительно такой же как и в главной форме:

private Thread _downloader;
private string _imageURL = "";
Bitmap _sourceBitmap;

public string ImageURL
{
    get
    {
        return _imageURL;
    }
    set
    {
        _imageURL = value;
    }
}

public ImageViewForm()
{
    InitializeComponent();
}

void OnImageDownloaded(Bitmap bmp)
{
    _sourceBitmap = bmp;
    imageBox.Image = bmp;
    imageBox.Width = bmp.Width;
    imageBox.Height = bmp.Height;
}

private delegate void ImageDownloadedDelegate(Bitmap bmp);

void DownloadImage()
{
    try
    {
        do
        {
            if (ImageURL == null || ImageURL.Length == 0) break;
            WebRequest request = HttpWebRequest.Create(ImageURL);
            WebResponse response = request.GetResponse();
            Bitmap bmp = new Bitmap(response.GetResponseStream());
            response.Close();
            Invoke(new ImageDownloadedDelegate(OnImageDownloaded),
                new object[] { bmp });
        }
        while (false);
    }
    catch (ThreadAbortException)
    {
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

public void StartDownloading()
{
    _downloader = new Thread(new ThreadStart(DownloadImage));
    _downloader.Start();
}

private void ImageViewForm_Closing(object sender, CancelEventArgs e)
{
    if (_downloader != null)
    {
        _downloader.Abort();
    }
}

Теперь пишем обработчик нажатия кнопки открытия формы просмотра полноразмерного изображения в главной форме

private void showPictureButton_Click(object sender, EventArgs e)
{
    do
    {
        if (photoInfoListView.SelectedIndices.Count == 0) break;
        ListViewItem item = photoInfoListView.Items[photoInfoListView.SelectedIndices[0]];
        if (item == null) break;
        string url = (item.Tag as string);
        if (url == null || url.Length == 0) break;
        ImageViewForm dlg = new ImageViewForm();
        dlg.ImageURL = url;
        dlg.StartDownloading();
        dlg.ShowDialog();
    }
    while (false);
}

Ну вот, собственно и все. В результате мы получим вот такое приложение:


Исходный код тестового приложения, а также исполняемый файл можно скачать здесь.

M.

Mobile Application Blocks. Наконец-то дождались!

На CodePlex наконец-то появился первый релиз Mobile Application Block.

Mobile Application Block – это набор готовых решений типовых задач, возникающих при разработке приложений для мобильных устройств на платформе .NET Compact Framework.

Как пишут сами авторы, это порт Mobile Client Software Factory для Visual Studio 2008 с более легковесным механизмом Dependency Injection.

В текущей версии доступны такие блоки как:

  • Configuration
  • ConnectionMonitor
  • ContainerModel
  • DataAccess
  • DisconnectedAgent
  • EndpointCatalog
  • PasswordAuthentication

Диаграммы классов нескольких блоков можно посмотреть ниже:

Mobile Application Block - Configuration - Class Diagram
Mobile Application Block - Configuration - Class Diagram
Mobile Application Block - ContainerModel - Class Diagram
Mobile Application Block - ContainerModel - Class Diagram

Mobile Application Block - Disconnected Agent - Class Diagram
Mobile Application Block - Disconnected Agent - Class Diagram

Все это добро весит совсем немного, после установки для каждого блока создается Visual Studio Solution с примером и юнит-тестами, так что процесс ознакомления с функционалом, предоставляемым MAB должен пройти быстро и безболезненно.

Скачать Mobile Application Block можно на странице проекта.