Надень шкуру на окно

Ты, наверное, частенько видел, как у разного софта можно было менять пользовательский интерфейс за счет, так называемых skins или шкурок по-русски. Хочешь научится делать свои окошки изменяемыми? ОК! Тогда я покажу тебе, насколько просто и легко, это реализуется. Мы с тобой создадим небольшую библиотеку, которую ты сможешь подключать к своим проектам, и затем, написав всего две строчки кода, заставишь свои окошки и кнопки быть такими, какими ты захочешь – без затрагивания исходников.Автор: John Frost
[attachment=39]

Ты, наверное, частенько видел, как у разного софта можно было менять пользовательский интерфейс за счет, так называемых skins или шкурок по-русски. Хочешь научится делать свои окошки изменяемыми? ОК! Тогда я покажу тебе, насколько просто и легко, это реализуется. Мы с тобой создадим небольшую библиотеку, которую ты сможешь подключать к своим проектам, и затем, написав всего две строчки кода, заставишь свои окошки и кнопки быть такими, какими ты захочешь – без затрагивания исходников.

Для начала определимся, что же мы хотим.
1) Возможности для окна
А) Изменение цвета фона.
Б) Изменение заголовка
В) Изменение фоновой картинки
Г) Изменение положения картинки (растянуто, замостить, посередине и т.д.)
2) Возможности для контролов
А) Имя контрола (button1 к примеру), не изменяется
Б) Изменение заголовка
В) Изменение цвета фона
Г) Изменение положения
Д) Изменение размера
Е) Изменение видимости (можно будет скрывать или отображать, меня тем самым функциональность)
Ж) Изменение доступности (блокированный или доступный элемент)
И) Изменение привязки (по левому краю, во всю область и т.д.)
К) Изменение шрифта и его размера
Л) Изменение цвета текста
М) Изменение фоновой картинки
Н) Изменение положения картинки (растянуто, замостить, посередине и т.д.)

Обязательным условием является то, что встроить поддержку шкурок в любой проект должно быть элементарно (несколько строк кода), и не должно предъявляться никаких требований к ранее созданным окнам. Т.е. как есть, так и есть.
Так, теперь подумаем, как же мы будем все это реализовывать. Control — определяет базовый класс для элементов управления, являющихся компонентами с визуальным представлением. Вспомним, как создается и настраивается типичный контрол на форме в коде:

private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.groupBox1 = new System.Windows.Forms.GroupBox();
//т.д.
this.groupBox1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
            this.groupBox1.Location = new System.Drawing.Point(12, 12);
            this.groupBox1.Name = "groupBox1";
            this.groupBox1.Size = new System.Drawing.Size(536, 175);
            this.groupBox1.TabIndex = 0;
            this.groupBox1.TabStop = false;
            this.groupBox1.Text = "Объекты";
}

Это стандартный код, который генерится Visual Studio’ей. Если по началу у вас появится стойкое и непреодолимое желание перегрузить эту функцию и вставить туда код загрузки скинов – то пожалуйста, обратитесь к психотерапевту. К чему я привел этот код? Да к тому, что как видишь, все параметры можно изменять на лету, т.е. пусть вызывается функция инициализации, затем мы отловливаем события загрузки формы и там уже меняем нужным элементам их свойства.
Так стоп! Что загружать, если мы еще не сохранили данные о форме в файл? Значит займемся сохранением (о да, детка, давай займемся сохранением!). Чтобы не мучиться с придумыванием формата, заюзаем XML. И поможет нам в этом бодрый и хороший класс XmlWriter,он представляет средство записи, обеспечивающее быстрый прямой способ (без кэширования) создания потоков файлов, содержащих XML-данные.
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = true;
settings.ConformanceLevel = ConformanceLevel.Auto;
writer = XmlWriter.Create(Path+»»+f.Name+».jfskin», settings);

Для начала мы создаем объект для настроек, затем создаем сам ХmlWriter, и заметь создаем мы его не конструктором, это абстрактный класс, мы вызываем статический метод, который и возвращает нам экземпляр этого класса. Теперь, чтобы записать свойство Text в файл, мы должны выполнить вот такой код:
writer.WriteStartElement(«Text»);
writer.WriteElementString(«value», c.Text);
writer.WriteEndElement();

В файле это будет выглядеть вот так:

<Text>
    <value>ЙА АЦЦКАЯ ПРОГРАММА</value>
</Text>

Вот мы и готовы к тому, чтобы написать функцию, которая сохраняет в файл нужные свойства одного контрола. Главное не забыть, что любой контрол может тоже содержать в себе контролы, поэтому функция будет рекурсивно вызывать сама себя, если внутри контрола, будут еще контролы (это предложение взрывает мне мозг).

private void SaveControl(Control c)
       {
           if (c.Name == "")//не сохраняем если нет имени у элемента
           {
               return;
           }

           writer.WriteStartElement("Control");//первый элемент

           writer.WriteStartElement("Name");
           writer.WriteElementString("value", c.Name);
           writer.WriteEndElement();

           writer.WriteStartElement("Text");
           writer.WriteElementString("value", c.Text);
           writer.WriteEndElement();

           writer.WriteStartElement("BackColor");
           writer.WriteElementString("value", c.BackColor.R + ";" + c.BackColor.G + ";" + c.BackColor.B);
           writer.WriteEndElement();

           writer.WriteStartElement("Location");
           writer.WriteElementString("value", c.Location.X.ToString() + ";" + c.Location.Y.ToString());
           writer.WriteEndElement();

           writer.WriteStartElement("Size");
           writer.WriteElementString("value", c.Size.Width.ToString() + ";" + c.Size.Height.ToString());
           writer.WriteEndElement();

           writer.WriteStartElement("Visible");
           writer.WriteElementString("value", c.Visible.ToString());
           writer.WriteEndElement();

           writer.WriteStartElement("Enabled");
           writer.WriteElementString("value", c.Enabled.ToString());
           writer.WriteEndElement();

           writer.WriteStartElement("Dock");
           writer.WriteElementString("value", (c.Dock).ToString());
           writer.WriteEndElement();

           writer.WriteStartElement("Font");
           writer.WriteElementString("value", (c.Font.FontFamily).Name + ";" + c.Font.Size.ToString() + ";" + c.Font.Bold.ToString() + ";" + c.Font.Italic.ToString());
           writer.WriteEndElement();

           writer.WriteStartElement("ForeColor");
           writer.WriteElementString("value", c.ForeColor.R + ";" + c.ForeColor.G + ";" + c.ForeColor.B);
           writer.WriteEndElement();

           writer.WriteStartElement("BackgroundImage");
           writer.WriteElementString("value", " ");
           writer.WriteEndElement();

           writer.WriteStartElement("BackgroundImageLayout");
           writer.WriteElementString("value", (c.BackgroundImageLayout).ToString());
           writer.WriteEndElement();


           writer.WriteEndElement();
           foreach (Control con in c.Controls)
           {
               SaveControl(con);
           }
       }

Почему мы не сохраняем если нету имени у котрола? Да потому что по имени мы и будет потом загружать эти данные. Так, теперь чтобы сохранить все контролы, какие есть на форме и саму форму, мы должны будем всеголишь выполнить такой код:

public  bool SaveForm(Form f)//сохранить все настройки элементов формы в файл
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.NewLineOnAttributes = true;
            settings.ConformanceLevel = ConformanceLevel.Auto;
            writer = XmlWriter.Create(Path+""+f.Name+".jfskin", settings);
           // writer.WriteStartDocument();
            writer.WriteStartElement("Form");
            //-----------------------------
            writer.WriteStartElement("BackColor");
            writer.WriteElementString("value", f.BackColor.R + ";" + f.BackColor.G + ";" + f.BackColor.B);
            writer.WriteEndElement();

            writer.WriteStartElement("Text");
            writer.WriteElementString("value", f.Text);
            writer.WriteEndElement();

            writer.WriteStartElement("BackgroundImage");
            writer.WriteElementString("value", " ");
            writer.WriteEndElement();

            writer.WriteStartElement("BackgroundImageLayout");
            writer.WriteElementString("value", (f.BackgroundImageLayout).ToString());
            writer.WriteEndElement();
            //--------------------------
            writer.WriteEndElement();
            foreach (Control c in f.Controls)
            {

                SaveControl(c);
              
            }
          // writer.WriteEndDocument();
            writer.Flush();
            writer.Close();
            return true;
        }

В папке с бинарником появится файл примерно вот такого содержания:
Надень шкуру на окно
Все, с сохранение разобрались, теперь попробуем написать метод, который загружает эти данные. Процесс чтения, как вы уже наверное догадались, происходит с помощью XmlReader и выглядит примерно вот так:

reader.ReadStartElement("Text");
c.Text = reader.ReadElementString();
reader.ReadEndElement();

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

public Skin(string exepath)//конструктор, принимает путь откуда экзе запускается
        {
            Path = exepath;
            skins = GetAvailableSkins();
        }
        public DirectoryInfo[] GetAvailableSkins()//дать список доступных шкурок
        {
            DirectoryInfo di = new DirectoryInfo(Path+"skins");
            DirectoryInfo[] ret = di.GetDirectories();
            return ret;
        }  

Ну вот и готово! Теперь в событии открытия формы прописываем вот эту строчку:

skin.SaveForm(this);

Форма сохранится, создаем папку в skins, кидаем туда этот файл, затем заменяем предыдущую строку на:


skin.CurrentSkin = "blackskin";
skin.OpenForm(this);

Изменяем в скине нужные параметры, запускаем и наслаждаемся!
Надень шкуру на окно