Еще одна интересная статья от Андрея Коновалова. В статье рассмотрены особенности реализации отрисовки изображений с прозрачностью при использовании .NET Compact Framework.

Вступление

К большому сожалению разработчиков, Compact Framework, да и native-функции тоже, не поддерживают альфа-канал с разной прозрачностью у индивидуальных пикселей. Это означает, что нет возможности создавать красоту неописуемую с плавными переходами между изображениями. Однако, что же делать, если хочется иметь хотя бы подобие “полного” альфа-канала, а именно, выводить полупрозрачные изображения, у которых есть ещё и полностью прозрачные участки?

Рассмотрим два способа вывода изображений с прозрачностью.

Способ №1. Фиксированый цвет является прозрачным

public static void DrawImageTransparent(Graphics g, 
  Bitmap b, Point location, Color transColor)
{
  if (b == null || g == null)
    return;

  ImageAttributes attrib = new ImageAttributes();
  attrib.SetColorKey(transColor, transColor);

  Rectangle destRect = new Rectangle(location.X, location.Y, b.Width, b.Height);
 
  g.DrawImage(b, destRect, 0, 0, b.Width, b.Height, GraphicsUnit.Pixel, attrib);
}

Стоит заметить, что только эта хитрая разновидность DrawImage позволяет выводить изображение с указанным ColorKey, по которому определяется, какие пиксели не рисовать. Шикарный набор параметров, не находите? 🙂 Куда рисовать, мы задаём через Rectange, а откуда — через 4 параметра. Ну это я так, лирическое отступление в сторону Microsoft.

Собственно, именно DrawImageTransparent и есть основной способ рисования изображений с прозрачными пикселями. Однако минус этого способа очевиден, состояния прозрачности всего два: полностью прозрачно и совсем непрозрачно.

Пример:

Compact Framework - Грани прозрачности
На самом деле, вполне неплохо, можно на этом и остановиться. Но хочется-то большего 🙂

Способ №2. У всего изображения фиксированный коэффициент непрозрачности

В этом случае без DllImport уже не обойтись, приготовим всё, что для этого необходимо:

public struct BlendFunction
{
  public byte BlendOp;
  public byte BlendFlags;
  public byte SourceConstantAlpha;
  public byte AlphaFormat;
}

public enum BlendOperation : byte
{
  AC_SRC_OVER = 0x00
}

public enum BlendFlags : byte
{
  Zero = 0x00
}

public enum SourceConstantAlpha : byte
{
  Transparent = 0x00,
  Opaque = 0xFF
}

public enum AlphaFormat : byte
{
  AC_SRC_ALPHA = 0x01
}

public class PlatformAPI
{
  [DllImport("coredll.dll")]
  extern public static Int32 AlphaBlend(IntPtr hdcDest, 
    Int32 xDest, Int32 yDest, Int32 cxDest, Int32 cyDest, 
    IntPtr hdcSrc, Int32 xSrc, Int32 ySrc, Int32 cxSrc, 
    Int32 cySrc, BlendFunction blendFunction);        
}

Как видно, обрезано всё, что только можно обрезать — в enum-ах по одному параметру и т.д. Но тем не менее, продолжаем. Собственно, наша функция:

public static void DrawAlpha(Graphics g, Bitmap b, Point location, byte opacity)
{
  if (b == null || g == null)
    return;

  using (Graphics gxSrc = Graphics.FromImage(g))
  {
    IntPtr hdcDst = g.GetHdc();
    IntPtr hdcSrc = gxSrc.GetHdc();
    BlendFunction blendFunction = new BlendFunction();
    blendFunction.BlendOp = (byte)BlendOperation.AC_SRC_OVER;
    blendFunction.BlendFlags = (byte)BlendFlags.Zero;
    blendFunction.SourceConstantAlpha = opacity;
    blendFunction.AlphaFormat = (byte)0;    
    PlatformAPI.AlphaBlend(hdcDst, location.X, location.Y, 
      b.Width, b.Height, hdcSrc, 0, 0, b.Width, b.Height, blendFunction);
    g.ReleaseHdc(hdcDst);
    gxSrc.ReleaseHdc(hdcSrc);
  }
}

Небольшие комментарии по коду — параметры у BlendFunction нельзя менять, они проставляются единственно возможные. Это обидно, но делать нечего.

Пример:
Compact Framework - грани прозрачности
Жутковато, да? Противные фиолетовые пиксели никуда не делись и тоже стали немного прозрачными 🙁

Комбинированное использование обоих способов

Вариантов комбинирования у нас, к сожалению, немного. На первый взгляд их совсем нет 🙂 Но есть всё-таки один способ.

Итак, решение следующее. Раз мы не можем одновременно задать ColorKey и вызвать AlphaBlend, будем использовать их по очереди. Сначала нарисуем фон стандартным спосбом без изысков, затем кнопку первым спосбом, а в конце… вторым спосбом нарисуем поверх фон с небольшим коэффициентом непрозрачности!

g.DrawImage(background, 0, 0);
DrawImageTransparent(g, button, new Point(10, 10), Color.FromArgb(255, 0, 255));
DrawAlpha(g, background, new Point(0, 0), 75);

Результат:
Compact Framework - Грани прозрачности

Описанный выше способ вполне жизнеспособен. Я им пользуюсь и вполне удовлетворён скоростью работы — на отрисовку всех элементов интерфейса в подобном стиле уходит в среднем от 60 до 80 миллисекунд (проверялось на разнообразных устройствах). Для создания приложения в таком стиле, безусловно, стандартные контролы не подойдут, но а кто обещал, что будет легко? В любом случае, для создания неописуемой красоты без собственного фреймворка рендеринга графических элементов не обойтись.

Оригинал статьи на Хабре.

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

This post has 2 Comments

2
  1. а можете уточнить пожалуйста что имело ввиду под этими словами “собственного фреймворка рендеринга графических элементов”?? что для красивого интерфейса не стоит использовать написаные вручную элементы управления? то есть лучше иметь один элемент управления и в нем все отрисовывать ручками?то есть не используя кучу специальных классических пользовательских элементов управления?

  2. Я думаю, здесь имелось в виду owner-drawn’ая версия каждого контрола. Хотя в принципе вариант когда одним контролом эмулируется работа всего интерфейса пользователя тоже не так и плох, для простых приложений это было бы нормально. В OpenGL-играх ведь тоже те же меню рендерятся в одной сцене на одном контроле.

Leave a Reply

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

M.

Mobile MVC Framework – Большое в малом – Часть I

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

Удачным и довольно эффективным способом сократить время, необходимое на разработку архитектуры, является использование шаблонов проектирования (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());
            }
        }
    }
}

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

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