- PVSM.RU - https://www.pvsm.ru -

Windows Phone 8: Создаем приложение. Матрица. Часть 3. MVVM

И так, как и обещал, третья часть приложения Матрица для платформы WP8 с использованием паттерна MVVM. Для самых нетерпеливых можно сразу скачать с Github [1], а так же опробовать у себя на смартфоне, скачав с Windows Phone Store [2].

Поддерживаются расширения экранов HD720P (720x1280), WVGA (480x800), WXGA (768x1280). Есть маленький минус: при использовании расширения отличного от 480x800, нужно перезапустить приложения после установки, так как не нашел как получить высоту и ширину элемента Grid до загрузки модели вида. А теперь по порядку.

Windows Phone 8: Создаем приложение. Матрица. Часть 1 [3]
Windows Phone 8: Создаем приложение. Матрица. Часть 2 [4]
Windows Phone 8: Создаем приложение. Матрица. Часть 3. MVVM [5]

Windows Phone 8: Создаем приложение. Матрица. Часть 3. MVVM

Развернутый вид всего приложения на экране WVGA (480x800)


Обзор функционала приложения на экране WVGA (480x800)

Про MVVM

Используем стандартный шаблон «Панорма с MVVM». Но подправим так, что б все тянулось из БД. Для этого создадим папочку «Models» и «DB». В Models будут определения моделей, а в DB для удобства классы для начальной инициализации значениями этих моделей, создания БД, проверки ее на заполненность, и конечно же класс наследующий DataContext. Однако в классе, наследующем DataContext, SeDataContext не подключаем одну модель — Model_Matrixes, так как мы не будем сохранять ее значения в БД. Она нужна лишь для того, что б создавать сетку матрицы и выводить в нее падающие змейки.

Дерево проекта

Windows Phone 8: Создаем приложение. Матрица. Часть 3. MVVM

Папочка «Models»

  • ModelBase — базовая модель, которую наследуют все модели. Просто реализует интерфейсы INotifyPropertyChanged, INotifyPropertyChanging, которые необходимы для возможности связывания модели с UI в режимах One/Two way.
    ModelBase

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.Models
    {
        public class ModelBase : INotifyPropertyChanged, INotifyPropertyChanging
        {
            #region INotifyPropertyChanged
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            // Used to notify that a property changed
            protected void NotifyPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            #endregion
    
            #region INotifyPropertyChanging
    
            public event PropertyChangingEventHandler PropertyChanging;
    
            // Used to notify that a property is about to change
            protected void NotifyPropertyChanging(string propertyName)
            {
                if (PropertyChanging != null)
                {
                    PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
                }
            }
    
            #endregion
        }
    }
    

    П.С. Про #region Слова .....#endregion.

    Если вдруг для кого это новое, то поясняю: просто для удобства. Ели в Visual Studio в редакторе нажать на «свернуть»(знак минус), то получим вот такое:
    Windows Phone 8: Создаем приложение. Матрица. Часть 3. MVVM

  • Model_Matrixes — модель сетки матрицы, в которой и будут происходить все манипуляции с символами. Связана с XAML кодом как «One way», тоесть при изменении значения модели, изменения сразу же отображаются и в UI. Так же одной из причин, почему она не подключена в SeDataContext то, что БД не поддерживает тип поля как SolidColorBrush, а с ним в итоге получилось работать удобней. Хотя писать конвертеры все равно пришлось.
    Model_Matrixes

    using System;
    using System.Collections.Generic;
    using System.Data.Linq.Mapping;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Media;
    
    namespace SE_Matrix_2d_v_14.Models
    {
        [Table]
        public class Model_Matrixes : ModelBase
        {
            // Define ID: private field, public property, and database column.
            private int _id;
            /// <summary>
            /// Таблица Model_Matrixes ID
            /// </summary>
            [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
            public int ID
            {
                get { return _id; }
                set
                {
                    if (_id != value)
                    {
                        NotifyPropertyChanging("ID");
                        _id = value;
                        NotifyPropertyChanged("ID");
                    }
                }
            }
    
            private string _matrix_Name;
            /// <summary>
            /// Таблица Model_Matrixes Matrix_Name
            /// </summary>
            [Column]
            public string Matrix_Name
            {
                get { return _matrix_Name; }
                set
                {
                    if (_matrix_Name != value)
                    {
                        NotifyPropertyChanging("Matrix_Name");
                        _matrix_Name = value;
                        NotifyPropertyChanged("Matrix_Name");
                    }
                }
            }
    
            private string _matrix_Text;
            /// <summary>
            /// Таблица Model_Matrixes Matrix_Text
            /// </summary>
            [Column]
            public string Matrix_Text
            {
                get { return _matrix_Text; }
                set
                {
                    if (_matrix_Text != value)
                    {
                        NotifyPropertyChanging("Matrix_Text");
                        _matrix_Text = value;
                        NotifyPropertyChanged("Matrix_Text");
                    }
                }
            }
    
            private int _matrix_FontSize;
            /// <summary>
            /// Таблица Model_Matrixes Matrix_FontSize
            /// </summary>
            [Column]
            public int Matrix_FontSize
            {
                get { return _matrix_FontSize; }
                set
                {
                    if (_matrix_FontSize != value)
                    {
                        NotifyPropertyChanging("Matrix_FontSize");
                        _matrix_FontSize = value;
                        NotifyPropertyChanged("Matrix_FontSize");
                    }
                }
            }
    
            private SolidColorBrush _matrix_Foreground;
            /// <summary>
            /// Таблица Model_Matrixes Matrix_Foreground
            /// </summary>
            [Column]
            public SolidColorBrush Matrix_Foreground
            {
                get { return _matrix_Foreground; }
                set
                {
                    if (_matrix_Foreground != value)
                    {
                        NotifyPropertyChanging("Matrix_Foreground");
                        _matrix_Foreground = value;
                        NotifyPropertyChanged("Matrix_Foreground");
                    }
                }
            }
        }
    }
    

  • Model_Colors — модель, отвечающая за настройки цвета.
    Model_Colors

    using System;
    using System.Collections.Generic;
    using System.Data.Linq.Mapping;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Media;
    
    namespace SE_Matrix_2d_v_14.Models
    {
        [Table]
        [DataContract]
        public class Model_Colors : ModelBase
        {
            // Define ID: private field, public property, and database column.
            private int _id;
            /// <summary>
            /// Таблица Model_Colors ID
            /// </summary>
            [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
            public int ID
            {
                get { return _id; }
                set
                {
                    if (_id != value)
                    {
                        NotifyPropertyChanging("ID");
                        _id = value;
                        NotifyPropertyChanged("ID");
                    }
                }
            }
    
            private string _name;
            /// <summary>
            /// Таблица Model_Colors Name
            /// </summary>
            [Column]
            public string Name
            {
                get { return _name; }
                set
                {
                    if (_name != value)
                    {
                        NotifyPropertyChanging("Name");
                        _name = value;
                        NotifyPropertyChanged("Name");
                    }
                }
            }
    
            private string _nameForTranslate;
            /// <summary>
            /// Таблица Model_Colors NameForTranslate
            /// </summary>
            [Column]
            public string NameForTranslate
            {
                get { return _nameForTranslate; }
                set
                {
                    if (_nameForTranslate != value)
                    {
                        NotifyPropertyChanging("NameForTranslate");
                        _nameForTranslate = value;
                        NotifyPropertyChanged("NameForTranslate");
                    }
                }
            }
    
            private string _value;
            /// <summary>
            /// Таблица Model_Colors Value
            /// </summary>
            [Column]
            public string Value
            {
                get { return _value; }
                set
                {
                    if (_value != value)
                    {
                        NotifyPropertyChanging("Value");
                        _value = value;
                        NotifyPropertyChanged("Value");
                    }
                }
            }
        }
    }
    

  • Model_SettingsSymbols — модель, отвечающая за цифровые настройки, такие как количество одновременных змеек при нажатии, длина змейки и др.
    Model_SettingsSymbols

    using System;
    using System.Collections.Generic;
    using System.Data.Linq;
    using System.Data.Linq.Mapping;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.Models
    {
        [Table()]
        public class Model_SettingsSymbols : ModelBase
        {
            // Define ID: private field, public property, and database column
            private int _id;
            /// <summary>
            /// Таблица Model_SettingsSymbols ID
            /// </summary>
            [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
            public int ID
            {
                get { return _id; }
                set
                {
                    if (_id != value)
                    {
                        NotifyPropertyChanging("ID");
                        _id = value;
                        NotifyPropertyChanged("ID");
                    }
                }
            }
    
            private string _param_Name;
            /// <summary>
            /// Таблица Model_SettingsSymbols Param_Name
            /// </summary>
            [Column]
            public string Param_Name
            {
                get { return _param_Name; }
                set
                {
                    if (_param_Name != value)
                    {
                        NotifyPropertyChanging("Param_Name");
                        _param_Name = value;
                        NotifyPropertyChanged("Param_Name");
                    }
                }
            }
    
            private int _param_Value;
            /// <summary>
            /// Таблица Model_SettingsSymbols Param_Value
            /// </summary>
            [Column]
            public int Param_Value
            {
                get { return _param_Value; }
                set
                {
                    if (_param_Value != value)
                    {
                        NotifyPropertyChanging("Param_Value");
                        _param_Value = value;
                        NotifyPropertyChanged("Param_Value");
                    }
                }
            }
    
            private string _nameForTranslate;
            /// <summary>
            /// Таблица Model_SettingsSymbols NameForTranslate
            /// </summary>
            [Column]
            public string NameForTranslate
            {
                get { return _nameForTranslate; }
                set
                {
                    if (_nameForTranslate != value)
                    {
                        NotifyPropertyChanging("NameForTranslate");
                        _nameForTranslate = value;
                        NotifyPropertyChanged("NameForTranslate");
                    }
                }
            }
    
            // Version column aids update performance.
            [Column(IsVersion = true)]
            private Binary _version;
        }
    }
    

  • Module_Languages — модель, отвечающая за язык, из которого берутся символы для матрицы.
    Module_Languages

    using System;
    using System.Collections.Generic;
    using System.Data.Linq.Mapping;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.Models
    {
        [Table]
        public class Module_Languages : ModelBase
        {
            // Define ID: private field, public property, and database column.
            private int _id;
            /// <summary>
            /// Таблица Model_SettingsSymbols ID
            /// </summary>
            [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
            public int ID
            {
                get { return _id; }
                set
                {
                    if (_id != value)
                    {
                        NotifyPropertyChanging("ID");
                        _id = value;
                        NotifyPropertyChanged("ID");
                    }
                }
            }
    
            // Имя параметра
            private string _name;
            /// <summary>
            /// Таблица Model_SettingsSymbols ID
            /// </summary>
            [Column]
            public string Name
            {
                get { return _name; }
                set
                {
                    if (_name != value)
                    {
                        NotifyPropertyChanging("Name");
                        _name = value;
                        NotifyPropertyChanged("Name");
                    }
                }
            }
    
            // Имя параметра
            private string _nameForTranslate;
            /// <summary>
            /// Таблица Model_SettingsSymbols ID
            /// </summary>
            [Column]
            public string NameForTranslate
            {
                get { return _nameForTranslate; }
                set
                {
                    if (_nameForTranslate != value)
                    {
                        NotifyPropertyChanging("NameForTranslate");
                        _nameForTranslate = value;
                        NotifyPropertyChanged("NameForTranslate");
                    }
                }
            }
    
            // Значение параметра
            private int _valueFrom;
            /// <summary>
            /// Таблица Model_SettingsSymbols ValueFrom
            /// </summary>
            [Column]
            public int ValueFrom
            {
                get { return _valueFrom; }
                set
                {
                    if (_valueFrom != value)
                    {
                        NotifyPropertyChanging("ValueFrom");
                        _valueFrom = value;
                        NotifyPropertyChanged("ValueFrom");
                    }
                }
            }
    
            // Значение параметра
            private int _valueTo;
            /// <summary>
            /// Таблица Model_SettingsSymbols ValueTo
            /// </summary>
            [Column]
            public int ValueTo
            {
                get { return _valueTo; }
                set
                {
                    if (_valueTo != value)
                    {
                        NotifyPropertyChanging("ValueTo");
                        _valueTo = value;
                        NotifyPropertyChanged("ValueTo");
                    }
                }
            }
    
            private bool _selected;
            /// <summary>
            /// Таблица Model_SettingsSymbols Selected
            /// </summary>
            [Column]
            public bool Selected
            {
                get { return _selected; }
                set
                {
                    if (_selected != value)
                    {
                        NotifyPropertyChanging("Selected");
                        _selected = value;
                        NotifyPropertyChanged("Selected");
                    }
                }
            }
        }
    }
    

Кратко о моделях: Описываем таблицы как в подходе «Code first». Каждая модель — это таблица в БД. Каждое свойство модели — это столбец.
В свойстве «set»: NotifyPropertyChanging и NotifyPropertyChanged — события, возникающие перед моментом начала изменений в модели и после изменений соответственно. Более подробно можно почитать на MSDN: INotifyPropertyChanged [6] и INotifyPropertyChanging [7].

Папочка «DB»

  • Класс SeDataContext — наследует DataContext [8]
    SeDataContext

    using SE_Matrix_2d_v_14.Models;
    using System;
    using System.Collections.Generic;
    using System.Data.Linq;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.DB
    {
        /// <summary>
        /// Конетекст БД
        /// </summary>
        public class SeDataContext : DataContext
        {
            // Pass the connection string to the base class.
            public SeDataContext(string connectionString)
                : base(connectionString)
            { }
    
            // Specify a table
            public Table<Model_SettingsSymbols> DB_SettingsSymbol;
            
            // Specify a table
            public Table<Module_Languages> DB_Language;
    
            // Specify a table
            public Table<Model_Colors> DB_Color;
        }
    }
    

  • Класс CheckDbExist — проверяет, существует ли нужная БД, если нет то создает, а так же заполняет таблицы начальными значениями.
    CheckDbExist

    using SE_Matrix_2d_v_14.Models;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.DB
    {
        public class CheckDbExist
        {
            /// <summary>
            /// Проверяет, существует ли заданная БД, и если нет - создает и заполняет начальными данными
            /// </summary>
            /// <param name="DBConnectionString">Строка подклчение к БД</param>
             public CheckDbExist(string DBConnectionString)
             {
                 // Create the database if it does not exist.
                 using (SeDataContext db = new SeDataContext(DBConnectionString))
                 {
                     if (db.DatabaseExists() == false)
                     {
                         db.CreateDatabase();
    
                         new FillSettingsSymbol(db);
                         new FillColors(db);
                         new FillLanguages(db);
    
                         db.SubmitChanges();
                     }
                 }
             }
        }
    }
    

    У Вас скорее всего возник вопрос, откуда вызывается этот класс и берется переменная DBConnectionString, в которой содержится строка подключения к БД. Ответ: App.xaml.cs.

    App.xaml.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Diagnostics;
    using System.Resources;
    using System.Windows;
    using System.Windows.Markup;
    using System.Windows.Navigation;
    using Microsoft.Phone.Controls;
    using Microsoft.Phone.Shell;
    using SE_Matrix_2d_v_14.Resources;
    using SE_Matrix_2d_v_14.ViewModels;
    using SE_Matrix_2d_v_14.Models;
    using SE_Matrix_2d_v_14.DB;
    using System.Windows.Media;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Text;
    
    namespace SE_Matrix_2d_v_14
    {
        public partial class App : Application
        {
            private static MainViewModel viewModel = null;
    
            // Строка соединения с БД
            string DBConnectionString = "Data Source=isostore:/Matrix333.sdf";
    
            /// <summary>
            /// Статический элемент ViewModel, используемый в представлениях для привязки.
            /// </summary>
            /// <returns>Объект MainViewModel.</returns>
            public static MainViewModel ViewModel
            {
                get
                {
                    return viewModel;
                }
            }
    
            /// <summary>
            /// Обеспечивает быстрый доступ к корневому кадру приложения телефона.
            /// </summary>
            /// <returns>Корневой кадр приложения телефона.</returns>
            public static PhoneApplicationFrame RootFrame { get; private set; }
    
            /// <summary>
            /// Конструктор объекта приложения.
            /// </summary>
            public App()
            {
                // Глобальный обработчик неперехваченных исключений.
                UnhandledException += Application_UnhandledException;
    
                // Стандартная инициализация XAML
                InitializeComponent();
    
                // Инициализация телефона
                InitializePhoneApplication();
    
                // Инициализация отображения языка
                InitializeLanguage();
    
                // Отображение сведений о профиле графики во время отладки.
                if (Debugger.IsAttached)
                {
                    // Отображение текущих счетчиков частоты смены кадров.
                    Application.Current.Host.Settings.EnableFrameRateCounter = true;
    
                    // Предотвратить выключение экрана в режиме отладчика путем отключения
                    // определения состояния простоя приложения.
                    // Внимание! Используйте только в режиме отладки. Приложение, в котором отключено обнаружение бездействия пользователя, будет продолжать работать
                    // и потреблять энергию батареи, когда телефон не будет использоваться.
                    PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
                }
    
                // Проверяем существует ли такая таблица, если нет создаем и заполняем начальными данными
                new CheckDbExist(DBConnectionString);
    
                // Вызываем модель вида
                viewModel = new MainViewModel(DBConnectionString);
    
                // Query the local database and load observable collections.
                viewModel.LoadData();
    
            }
    

  • Класс FillColors — заполняет таблицу Model_Colors начальными значениями.
    FillColors

    using SE_Matrix_2d_v_14.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.DB
    {
        public class FillColors
        {
            /// <summary>
            /// Заполняет таблицу Model_Colors начальными данными
            /// </summary>
            /// <param name="db">Объект БД, в которую занести изменения</param>
            public FillColors(SeDataContext db)
            {
                db.DB_Color.InsertOnSubmit(new Model_Colors { Name = "Param_Color_FirstSymbol", Value = "#FFF8F8FF", NameForTranslate = "a" });
                db.DB_Color.InsertOnSubmit(new Model_Colors { Name = "Param_Color_Background", Value = "#FF000000", NameForTranslate = "a" });
                db.DB_Color.InsertOnSubmit(new Model_Colors { Name = "Param_Color_GradientFrom", Value = "#FF00FF00", NameForTranslate = "a" });
                db.DB_Color.InsertOnSubmit(new Model_Colors { Name = "Param_Color_GradientTo", Value = "#FF00AA99", NameForTranslate = "a" });
            }
        }
    }
    

  • Класс FillLanguages — заполняет таблицу Module_Languages начальными значениями.
    FillLanguages

    using SE_Matrix_2d_v_14.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.DB
    {
        public class FillLanguages
        {
            /// <summary>
            /// Заполняет таблицу Model_Colors начальными данными
            /// </summary>
            /// <param name="db">Объект БД, в которую занести изменения</param>
            public FillLanguages(SeDataContext db)
            {
                db.DB_Language.InsertOnSubmit(new Module_Languages { Name = "English", ValueFrom = 64, ValueTo = 127, NameForTranslate = "a", Selected = true });
                db.DB_Language.InsertOnSubmit(new Module_Languages { Name = "Russion", ValueFrom = 1040, ValueTo = 1103, NameForTranslate = "a", Selected = false });
                db.DB_Language.InsertOnSubmit(new Module_Languages { Name = "Chinese", ValueFrom = 19968, ValueTo = 20223, NameForTranslate = "a", Selected = false });
                db.DB_Language.InsertOnSubmit(new Module_Languages { Name = "Numbers", ValueFrom = 48, ValueTo = 57, NameForTranslate = "a", Selected = false });
            }
        }
    }
    

  • Класс FillSettingsSymbol — заполняет таблицу Model_SettingsSymbols начальными значениями.
    FillSettingsSymbol

    using SE_Matrix_2d_v_14.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.DB
    {
        public class FillSettingsSymbol
        {
            /// <summary>
            /// Заполняет таблицу Model_Colors начальными данными
            /// </summary>
            /// <param name="db">Объект БД, в которую занести изменения</param>
            public FillSettingsSymbol(SeDataContext db)
            {
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_Iteration", Param_Value = 6, NameForTranslate = "a" });
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_CountSimultaneously", Param_Value = 3, NameForTranslate = "a" });
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_SpeedTo", Param_Value = 50, NameForTranslate = "a" });
                //db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_AddingSize", Param_Value = 2, NameForTranslate = "a" });
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_FontSize", Param_Value = 15, NameForTranslate = "a" });
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_MinLength", Param_Value = 5, NameForTranslate = "a" });
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_MaxLength", Param_Value = 15, NameForTranslate = "a" });
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_WindowWidth", Param_Value = 420, NameForTranslate = "a" });
                db.DB_SettingsSymbol.InsertOnSubmit(new Model_SettingsSymbols { Param_Name = "Param_WindowHeight", Param_Value = 671, NameForTranslate = "a" });
            }
        }
    }
    

Дополнительные библиотеки

Так как стандартного выбора цвета нет, то пришлось использовать библиотеку Coding4fun для элемента ColorPicker. Для списка языков использовал библиотеку Windows Phone Toolkit, которая содержит элемент ListPicker. Обе легко устанавливаются через NuGet.

MainPage.xaml

Теперь рассмотрим внешний вид приложения и Binding.

MainPage.xaml

<phone:PhoneApplicationPage
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:Controls="clr-namespace:Coding4Fun.Toolkit.Controls;assembly=Coding4Fun.Toolkit.Controls" 
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" 
    x:Class="SE_Matrix_2d_v_14.MainPage"
    mc:Ignorable="d"
    SupportedOrientations="Portrait"  Orientation="Portrait"
    shell:SystemTray.IsVisible="False">
    <!--   Resources      d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}" -->
    <phone:PhoneApplicationPage.Resources>        
        <!--   SettingsSymbols   -->
        <DataTemplate x:Key="Resources_SettingsSymbols">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="30"/>
                    <RowDefinition Height="70"/>
                </Grid.RowDefinitions>
                <TextBlock Grid.Row="0" Text="{Binding NameForTranslate}"  />
                <TextBox x:Name="TextBox_Param_Value"
        			Tag="{Binding ID}"
        			InputScope="Number"
        			Grid.Row="1" 
        			Width="300"
        			HorizontalAlignment="Left"
        			Text="{Binding Param_Value, Mode=TwoWay}" 
        			TextChanged="Event_TextBox_TextChanged_SettingSymbols"
        			LostFocus="Event_TextBox_LostFocus_SettingsSymbols"  />
            </Grid>
        </DataTemplate>

        <!--   SettingsColor   -->
        <DataTemplate x:Key="SettingsColor">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="25"/>
                    <RowDefinition Height="100"/>
                </Grid.RowDefinitions>
                <TextBlock Grid.Row="0" Text="{Binding NameForTranslate}" />
                <Rectangle Grid.Row="1" Fill="{Binding Value, Mode=TwoWay}" Tag="{Binding ID}" HorizontalAlignment="Left" Height="75" Margin="0,10,0,0"  Stroke="Transparent" VerticalAlignment="Top" Width="420" Tap="Event_Rectangle_Tap_ChangeColor"/>
            </Grid>
        </DataTemplate>

        <!--   Matrix   -->
        <DataTemplate x:Key="Matrix">
            <TextBlock  x:Uid="{Binding Matrix_Name}" 
        		Text="{Binding Matrix_Text, Mode=TwoWay}"
        		FontSize="{Binding Matrix_FontSize}"
        		Foreground="{Binding Matrix_Foreground}"                     
                       />
        </DataTemplate>

        <!--   ListPicker   -->
        <DataTemplate x:Key="ListPickerItemTemplate">
            <TextBlock Text="{Binding NameForTranslate}"  />
        </DataTemplate>
        <DataTemplate x:Key="ListPickerFullItemTemplate">
            <TextBlock Text="{Binding NameForTranslate}" FontSize="{StaticResource PhoneFontSizeExtraLarge}" />
        </DataTemplate>
    </phone:PhoneApplicationPage.Resources>

    <phone:PhoneApplicationPage.FontFamily>
        <StaticResource ResourceKey="PhoneFontFamilyNormal"/>
    </phone:PhoneApplicationPage.FontFamily>
    <phone:PhoneApplicationPage.FontSize>
        <StaticResource ResourceKey="PhoneFontSizeNormal"/>
    </phone:PhoneApplicationPage.FontSize>
    <phone:PhoneApplicationPage.Foreground>
        <StaticResource ResourceKey="PhoneForegroundBrush"/>
    </phone:PhoneApplicationPage.Foreground>

    <!-- LayoutRoot представляет корневую сетку, где размещается все содержимое страницы-->
    <Grid x:Name="LayoutRoot" Background="Transparent">

        <phone:Panorama>
            <phone:Panorama.Background>
                <ImageBrush ImageSource="/SE_Matrix_2d_v_14;component/Assets/PanoramaBackground.png"/>
            </phone:Panorama.Background>

            <!--Первый элемент Panorama  -->
            <phone:PanoramaItem Header="SE Матрица">
                <phone:LongListSelector
					Margin="0,0,0,5"
					ItemsSource="{Binding ItemSourceMatrix}"
					x:Name="LongListSelector_Matrix" 
					Loaded="Event_LongListSelector_OnLoaded_Matrix"
					ItemTemplate="{StaticResource Matrix}"
					GridCellSize="20, 20"
					LayoutMode="Grid"
					FontSize="10"
					Padding="10,0,-5,0" Tap="Event_LongListSelector_Tap_StartMatrix" 
					Background="{Binding  Path=ItemSourceMatrixBackground, Mode=TwoWay}"
                                         />
            </phone:PanoramaItem>

            <!--Второй элемент Panorama-->
            <phone:PanoramaItem Header="Настройка значений">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="30"/>
                        <RowDefinition Height="60"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <phone:LongListSelector Margin="0,0,-22,0"  Grid.Row="2"                                          
					    ItemsSource="{Binding ItemSourceSettingsSymbols}"
					    x:Name="LongListSelector_SettingsSymbols" 
					    ItemTemplate="{StaticResource Resources_SettingsSymbols}"  />
                    <toolkit:ListPicker 
                        x:Name="sightingTypesPicker"
                        ExpansionMode="FullScreenOnly" FullModeHeader="Выберите язык матрицы"
                        ItemsSource="{Binding ItemSourceLanguage}"
                        FullModeItemTemplate="{StaticResource ListPickerFullItemTemplate}"
                        ItemTemplate="{StaticResource ListPickerItemTemplate}"
                        Grid.Row="1" HorizontalAlignment="Left"             
                        SelectedItem="{Binding Path=ItemSourceLanguageSelected, Mode=OneWay}"
                        BorderThickness="0"
                        Height="60" Margin="0" VerticalAlignment="Top" Width="300"
                        SelectionChanged="Event_ListPicker_SelectionChanged"
                        />
                    <TextBlock Grid.Row="0" Text="Выберите язык матрицы"  />                   
                </Grid>
                
            </phone:PanoramaItem>
            <!-- Обратная связь    -->
            <phone:PanoramaItem Header="О приложении">
                <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="60"/>
                        <RowDefinition Height="60"/>
                        <RowDefinition Height="60"/>
                    </Grid.RowDefinitions>
                    <TextBlock Grid.Row="0" Text="SE Матрица 1.0" FontSize="{StaticResource PhoneFontSizeLarge}"></TextBlock>
                    <TextBlock Grid.Row="1" Text="se8se@hotmail.com" FontSize="{StaticResource PhoneFontSizeLarge}"></TextBlock>
                    <TextBlock Grid.Row="2" FontSize="{StaticResource PhoneFontSizeLarge}">© SE, 2013</TextBlock>
                </Grid>
            </phone:PanoramaItem>
            <!--Третий элемент Panorama    -->
            <phone:PanoramaItem Header="Настройка цвета">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="400"/>
                        <RowDefinition Height="20"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Controls:ColorPicker x:Name="ColorPicker" Grid.Row="0" Height="400" VerticalAlignment="Top"/>
                    <phone:LongListSelector Margin="0,0,-22,0"
						Grid.Row="2"
						ItemsSource="{Binding ItemSourceSettingsColors}"
						x:Name="LongListSelector_SettingsColor" 
						ItemTemplate="{StaticResource SettingsColor}" />
                </Grid>
            </phone:PanoramaItem>


        </phone:Panorama>
    </Grid>

</phone:PhoneApplicationPage>

Этот код формирует такую картинку, как в самом начале страницы. Никаких ухищрений нету, стандартный XAML, но если возникнут какие то вопросы — спрашивайте. Буду рад помочь.

Папочка «ViewModels»

Добрались до сердца нашего приложения. То, ради чего все и затевалось. И так, всего два класса.

  • Класс BaseViewModel — реализует интерфейс INotifyPropertyChanged
    BaseViewModel

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SE_Matrix_2d_v_14.ViewModels
    {
        public class BaseViewModel : INotifyPropertyChanged
        {
            #region INotifyPropertyChanging Members
    
            public event PropertyChangedEventHandler PropertyChanged;
            protected void NotifyPropertyChanged(String propertyName)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (null != handler)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            #endregion
        }
    }
    

  • Класс MainViewModel — наследует BaseViewModel, и реализует непосредственно всю логику. Возможно есть некоторые конструктивные ошибки недочеты, которые с радостью исправлю, если Вы укажете на них и так будет действительно лучше правильней.
    MainViewModel

    using SE_Matrix_2d_v_14.DB;
    using SE_Matrix_2d_v_14.Helpers;
    using SE_Matrix_2d_v_14.Models;
    using SE_Matrix_2d_v_14.Resources;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Media;
    
    
    namespace SE_Matrix_2d_v_14.ViewModels
    {
        public class MainViewModel : BaseViewModel
        {
            private SeDataContext db;
    
            public MainViewModel(string SeDBConnectionString)
            {
                db = new SeDataContext(SeDBConnectionString);
    
                ItemSourceSettingsSymbols = new ObservableCollection<Model_SettingsSymbols>();
                ItemSourceMatrix = new ObservableCollection<Model_Matrixes>();
                ItemSourceSettingsColors = new ObservableCollection<Model_Colors>();
                ItemSourceLanguage = new ObservableCollection<Module_Languages>();
            }
            
            #region ItemSourceSettingsSymbols
            /// <summary>
            /// ViewModel. Коллекция объектов ItemSourceSettingsSymbols.
            /// </summary>
            private ObservableCollection<Model_SettingsSymbols> _itemSourceSettingsSymbols;
            public ObservableCollection<Model_SettingsSymbols> ItemSourceSettingsSymbols
            {
                get { return _itemSourceSettingsSymbols; }
                set
                {
                    _itemSourceSettingsSymbols = value;
                    NotifyPropertyChanged("ItemSourceSettingsSymbols");
                }
            }
            #endregion
    
            #region ItemSourceSettingsColors
            /// <summary>
            /// ViewModel. Коллекция объектов ItemSourceSettingsColors.
            /// </summary>
            private ObservableCollection<Model_Colors> _itemSourceSettingsColors;
            public ObservableCollection<Model_Colors> ItemSourceSettingsColors
            {
                get { return _itemSourceSettingsColors; }
                set
                {
                    _itemSourceSettingsColors = value;              
                    NotifyPropertyChanged("ItemSourceSettingsColors");
                }
            }
            #endregion
    
            #region ItemSourceMatrix
            /// <summary>
            /// ViewModel. Коллекция объектов ItemSourceMatrix.
            /// </summary>
            private ObservableCollection<Model_Matrixes> _itemSourceMatrix;
            public ObservableCollection<Model_Matrixes> ItemSourceMatrix
            {
                get { return _itemSourceMatrix; }
                set
                {
                    _itemSourceMatrix = value;
                    NotifyPropertyChanged("ItemSourceMatrix");
                }
            }
            #endregion
    
            #region ItemSourceMatrixBackground
            /// <summary>
            /// ViewModel. Коллекция объектов ItemSourceMatrixBackground.
            /// </summary>
            public SolidColorBrush _itemSourceMatrixBackground;
            public SolidColorBrush ItemSourceMatrixBackground { 
                get{return _itemSourceMatrixBackground;}
                set {
                    _itemSourceMatrixBackground = value;
                    NotifyPropertyChanged("ItemSourceMatrixBackground");
                }
            }
            #endregion
    
            #region ItemSourceLanguage
            /// <summary>
            /// ViewModel. Коллекция объектов ItemSourceMatrixBackground.
            /// </summary>
            private ObservableCollection<Module_Languages> _itemSourceLanguage;
            public ObservableCollection<Module_Languages> ItemSourceLanguage
            {
                get { return _itemSourceLanguage; }
                set
                {
                    _itemSourceLanguage = value;
                    NotifyPropertyChanged("ItemSourceLanguage");
                }
            }
            #endregion
    
            #region ItemSourceLanguageSelected
            /// <summary>
            /// ViewModel. Коллекция объектов ItemSourceMatrixBackground.
            /// </summary>
            private Module_Languages _itemSourceLanguageSelected;
            public Module_Languages ItemSourceLanguageSelected
            {
                get { return _itemSourceLanguageSelected; }
                set
                {
                    _itemSourceLanguageSelected = value;
                    NotifyPropertyChanged("ItemSourceLanguageSelected");
                }
            }
            #endregion
    
            /// <summary>
            /// ViewModel. Сохранение в БД
            /// </summary>
            public void SaveChangesToDB()
            {
                db.SubmitChanges();
            }
    
            public bool IsDataLoaded
            {
                get;
                private set;
            }
          
            int coef = 20;
            /// <summary>
            /// ViewModel. Создает и добавляет несколько объектов ViewModel в коллекцию элементов.
            /// </summary>
            public void LoadData()
            {
                // Получаем данные из БД по таблице Model_SettingsSymbols
                IQueryable<Model_SettingsSymbols> InDB_SettingsSymbol = (from Model_SettingsSymbols todo in db.DB_SettingsSymbol
                                                                        select todo);
                         
                Param_WindowWidth = InDB_SettingsSymbol.Where(x => x.Param_Name == "Param_WindowWidth").FirstOrDefault().Param_Value;
                Param_WindowHeight = InDB_SettingsSymbol.Where(x => x.Param_Name == "Param_WindowHeight").FirstOrDefault().Param_Value;
                rowsNumber = (int)Math.Round(Param_WindowHeight / (decimal)coef, MidpointRounding.AwayFromZero);
                columnsNumber = (int)Math.Round(Param_WindowWidth / (decimal)coef, MidpointRounding.AwayFromZero) - 1;
    
                // Заполняем ItemSourceSettingsSymbols, которая связанна со страницей настроеек, которые необходимо вводить как числа
                _itemSourceSettingsSymbols = new SE_FillOC().FillSettingsSymbols(InDB_SettingsSymbol.Where(x => x.Param_Name != "Param_WindowWidth" && x.Param_Name != "Param_WindowHeight"));
    
                // Создаем сетку, где будет выводиться матрица
                _itemSourceMatrix = new SE_FillOC().FillMatrixes(rowsNumber, columnsNumber);
    
                // Получаем данные из БД по таблице Model_Colors
                IQueryable<Model_Colors> InDB_Colors = (from Model_Colors todo in db.DB_Color
                                                        select todo);
    
                // Заполняем ItemSourceSettingsColors, которая связана с настройками цветов
                _itemSourceSettingsColors = new SE_FillOC().FillColors(InDB_Colors);
    
                // Получаем данные из БД по таблице Module_Languages
                IQueryable<Module_Languages> InDB_Languages = (from Module_Languages todo in db.DB_Language
                                                               select todo);
    
                // Заполняем ItemSourceLanguage, которая связана с настройками языка символов матрицы
                _itemSourceLanguage = new SE_FillOC().FillLanguages(InDB_Languages);
    
                // Инициализируем цветфона матрицы
                ItemSourceMatrixBackground = SE_Colors.StringToBrush(ItemSourceSettingsColors.Where(x => x.Name == "Param_Color_Background").SingleOrDefault().Value);
    
                // Устанавливаем язык символов и символы матрицы из БД
                ItemSourceLanguageSelected = ItemSourceLanguage.Where(z => z.Selected == true).SingleOrDefault();
                LanguageFrom = ItemSourceLanguage.Where(z => z.Selected == true).SingleOrDefault().ValueFrom;
                LanguageTo   = ItemSourceLanguage.Where(z => z.Selected == true).SingleOrDefault().ValueTo;
                this.IsDataLoaded = true;
            }
    
            #region UpdateSettingsSymbolsByID
            /// <summary>
            /// ViewModel. Обновляем настройки таблицы Model_SettingsSymbols в БД по ID
            /// </summary>
            public void UpdateSettingsSymbolsByID(Model_SettingsSymbols SettingsSymbolsForDelete)
            {
                var query = (from Model_SettingsSymbols todo in db.DB_SettingsSymbol
                             where todo.ID == SettingsSymbolsForDelete.ID
                             select todo).First();
    
                query.Param_Value = SettingsSymbolsForDelete.Param_Value;
    
                db.SubmitChanges();
    
                ItemSourceSettingsSymbols.Where(v => v.ID == SettingsSymbolsForDelete.ID).SingleOrDefault().Param_Value = SettingsSymbolsForDelete.Param_Value;
            }
            #endregion
    
            #region UpdateSettingsSymbolsByName
            /// <summary>
            /// ViewModel. Обновляем настройки таблицы Model_SettingsSymbols в БД по имени
            /// </summary>
            public void UpdateSettingsSymbolsByName(Model_SettingsSymbols SettingsSymbolsForDelete)
            {
                var query = (from Model_SettingsSymbols todo in db.DB_SettingsSymbol
                             where todo.Param_Name == SettingsSymbolsForDelete.Param_Name
                             select todo).SingleOrDefault();
    
                query.Param_Value = SettingsSymbolsForDelete.Param_Value;
    
                db.SubmitChanges();
            }
            #endregion
    
            #region UpdateSettingsColorByID
            /// <summary>
            /// ViewModel. Обновляем настройки таблицы Model_Colors в БД по ID
            /// </summary>
            public void UpdateSettingsColorByID(Model_Colors SettingsSymbolsForDelete)
            {
                var query = (from Model_Colors todo in db.DB_Color
                             where todo.ID == SettingsSymbolsForDelete.ID
                             select todo).First();
    
                query.Value = SettingsSymbolsForDelete.Value;
    
                db.SubmitChanges();
    
                ItemSourceSettingsColors.Where(v => v.ID == SettingsSymbolsForDelete.ID).SingleOrDefault().Value = SettingsSymbolsForDelete.Value;
    
                // При изменении фона матрицы обновляем свойство класса, с которым свзано свойство Background во View
                ItemSourceMatrixBackground = SE_Colors.StringToBrush(ItemSourceSettingsColors.Where(x => x.Name == "Param_Color_Background").SingleOrDefault().Value);
            }
            #endregion
    
            #region UpdateSelectedLanguageByID
            /// <summary>
            /// ViewModel. Обновляем настройки таблицы Module_Languages в БД по ID
            /// </summary>
            public void UpdateSettingsColorByID(Module_Languages forUpdate)
            {
                var query = (from Module_Languages todo in db.DB_Language
                             where todo.Selected == true
                             select todo).SingleOrDefault();
    
                query.Selected = false;
    
                db.SubmitChanges();
    
                var query1 = (from Module_Languages todo in db.DB_Language
                              where todo.ID == forUpdate.ID
                             select todo).SingleOrDefault();
    
                query1.Selected = true;
    
                db.SubmitChanges();
    
                // Обновляем язык символов и символы матрицы из БД
                ItemSourceLanguage.Where(c => c.Selected == true).SingleOrDefault().Selected = false;
                ItemSourceLanguage.Where(c => c.ID == forUpdate.ID).SingleOrDefault().Selected = true;
                ItemSourceLanguageSelected = ItemSourceLanguage.Where(z => z.Selected == true).SingleOrDefault();
                LanguageFrom               = ItemSourceLanguage.Where(z => z.Selected == true).SingleOrDefault().ValueFrom;
                LanguageTo                 = ItemSourceLanguage.Where(z => z.Selected == true).SingleOrDefault().ValueTo;
            }
            #endregion
    
            #region Class params
            public SolidColorBrush TheColorOfFirstSymbol { get; set; }
            public SolidColorBrush TheColorOfBackground { get; set; }
            public SolidColorBrush TheColorOfGradientFromBrush { get; set; }
            public SolidColorBrush TheColorOfGradientToBrush { get; set; }
            public Dictionary<string, int> TheColorOfGradientFromDictionary { get; set; }
            public Dictionary<string, int> TheColorOfGradientToDictionary { get; set; }
            int Param_Iteration { get; set; }
            int Param_MinLength { get; set; }
            int Param_MaxLength { get; set; }
            int Param_SpeedTo { get; set; }
            int rowsNumber { get; set; }
            int columnsNumber { get; set; }
            int Param_WindowWidth { get; set; }
            int Param_WindowHeight { get; set; }
            int Param_FontSize { get; set; }
            int LanguageFrom { get; set; }
            int LanguageTo { get; set; }
            Random random = new Random();
            SE_Colors SE_Colors = new SE_Colors();
            #endregion
    
            /// <summary>
            /// Нужно для возможности запускать одинм нажатием больше одной змейки одновременно
            /// </summary>
            public void Start()
            {          
                for (int i = 0; i < ItemSourceSettingsSymbols.Where(v => v.Param_Name == "Param_CountSimultaneously").SingleOrDefault().Param_Value; i++ )
                {
                     MoveMatrix();
                }          
            }
    
            /// <summary>
            /// ViewModel. Matrix. Функция входа в Матрицу. Задаются основные праматры
            /// и количество повторений змейки
            /// </summary>
            public async Task MoveMatrix()
            {
                Param_Iteration = ItemSourceSettingsSymbols.Where(v => v.Param_Name == "Param_Iteration").SingleOrDefault().Param_Value;
                Param_MinLength = ItemSourceSettingsSymbols.Where(v => v.Param_Name == "Param_MinLength").SingleOrDefault().Param_Value;
                Param_MaxLength = ItemSourceSettingsSymbols.Where(v => v.Param_Name == "Param_MaxLength").SingleOrDefault().Param_Value;
                Param_SpeedTo   = ItemSourceSettingsSymbols.Where(v => v.Param_Name == "Param_SpeedTo").SingleOrDefault().Param_Value;
                Param_FontSize  = ItemSourceSettingsSymbols.Where(v => v.Param_Name == "Param_FontSize").SingleOrDefault().Param_Value;
    
                TheColorOfFirstSymbol            =  SE_Colors.StringToBrush(ItemSourceSettingsColors.Where(z => z.Name == "Param_Color_FirstSymbol").SingleOrDefault().Value);
                TheColorOfBackground             =  SE_Colors.StringToBrush(ItemSourceSettingsColors.Where(x1 => x1.Name == "Param_Color_Background").SingleOrDefault().Value);
                TheColorOfGradientFromBrush      =  SE_Colors.StringToBrush(ItemSourceSettingsColors.Where(x2 => x2.Name == "Param_Color_GradientFrom").SingleOrDefault().Value);
                TheColorOfGradientToBrush        =  SE_Colors.StringToBrush(ItemSourceSettingsColors.Where(x3 => x3.Name == "Param_Color_GradientTo").SingleOrDefault().Value);
                TheColorOfGradientFromDictionary =  SE_Colors.StringToDictionary(ItemSourceSettingsColors.Where(x4 => x4.Name == "Param_Color_GradientFrom").SingleOrDefault().Value);
                TheColorOfGradientToDictionary   =  SE_Colors.StringToDictionary(ItemSourceSettingsColors.Where(x5 => x5.Name == "Param_Color_GradientTo").SingleOrDefault().Value);
    
                for (int i = 0; i < Param_Iteration; i++)
                {
                    // Начало змейки по горизонтали случайным образом
                    int ranX = random.Next(0, columnsNumber);
    
                    // Начало змейки по вертикали случайным образом
                    int ranY = random.Next( -Param_MaxLength , rowsNumber);
    
                    // Длина змейки случайным образом
                    int length = random.Next(Param_MinLength, Param_MaxLength);
    
                    // Скорость смены символов в змейке случайным образом
                    int time = random.Next(5, Param_SpeedTo);
                  
                    //Обработка змейки
                    await MoveMatrixElements(ranX, ranY, length, time);
                }
            }
    
            /// <summary>
            /// ViewModel. Matrix. Определяет какие и сколько элементов показывать и с какой скоростью
            /// </summary>
            /// <param name="x">Начало змейки по оси х</param>
            /// <param name="y">Начало змейки по оси у</param>
            /// <param name="length">Длина звейки</param>
            /// <param name="time">Время между добавлением нового символа к змейке</param>
            public async Task MoveMatrixElements(int x, int y, int length, int time)
            {
                // Словарь для хранения идентификаторов ячеек, которые вызывались на предыдущем этапе.
                Dictionary<int, Model_Matrixes> dicElem = new Dictionary<int, Model_Matrixes>();
    
                int count = 0;
                int fail = 0;
    
                for (int i = 0; i < length; i++)
                {
                    if ((y + i) < rowsNumber && (y + i) >= 0)
                    {
                        string elementNameToMove = x + "_" + (y + i);
                        Model_Matrixes elementToMove = ItemSourceMatrix.Where(xx => xx.Matrix_Name == elementNameToMove).SingleOrDefault();
                        dicElem[count] = (elementToMove);
    
                        await MatrixElementsChange(elementToMove, time, TheColorOfFirstSymbol);
    
                        // Перебираем все  элементы, составляющие змейку на данном этапе. С каждым циклом она увеличивается, пока не достигнет нужной длины.
                        for (int k = 0; k <= count; k++)
                        {
                            //Извлекаем элементы, которые должны следовать за самым ярким. Создаем эффект "затухания" цвета
                            Model_Matrixes previousElement = dicElem[k];
    
                            Dictionary<string, int> coefficientFromGradient = new SE_Colors().GetСoefficientFromGradient(TheColorOfGradientFromDictionary, TheColorOfGradientToDictionary, count);
                            SolidColorBrush colorSymbol = SE_Colors.GetSymbolColorForGradient(TheColorOfGradientFromDictionary, coefficientFromGradient, (i - fail - k));
    
                            Task dsvv = MatrixElementsChange(previousElement, time, colorSymbol);
                        }
                        count++;
                    }
                    else
                    {
                        // Что б сверху матрица красиво падала.
                        fail++;
                    }
                }
            }
    
            /// <summary>
            /// ViewModel. Matrix. Задает цвет, количество смены символов, затухание
            /// элементов матрицы
            /// /// </summary>
            /// <param name="element">Объект типа Model_Matrixes</param>
            /// <param name="timeOut">Время, сфомированное случайным образом на основе данных,
            /// введенных в настройках</param>
            /// <param name="NewColor">Цвет символа</param>
            public async Task MatrixElementsChange(Model_Matrixes element, int timeOut, SolidColorBrush NewColor)
            {
                element.Matrix_Text = RandomActualSymbol();
                element.Matrix_Foreground = NewColor;
                element.Matrix_FontSize = Param_FontSize;
                await Task.Delay(timeOut);
            }
    
            /// <summary>
            /// Случайным образом в зависимости от выбранного языка определяет символ
            /// </summary>
            /// <returns>Возвращаем символ, который вывести</returns>
            public string RandomActualSymbol()
            {
                // Выбираем случайнфй символ в диапазоне от первого до последнего символа в заданом языке
                return char.ConvertFromUtf32(this.random.Next((int)LanguageFrom, (int)LanguageTo));
            }
        }
    }
    

    Код достаточно сильно комментирован, так что должно быть понятно. Однако пробежимся:

                ItemSourceSettingsSymbols = new ObservableCollection<Model_SettingsSymbols>();
                ItemSourceMatrix = new ObservableCollection<Model_Matrixes>();
                ItemSourceSettingsColors = new ObservableCollection<Model_Colors>();
                ItemSourceLanguage = new ObservableCollection<Module_Languages>();
    

    Свойства модели, к которым привязываемся из нашего UI (XAML).
    Обращение к свойствам моделей происходит в стиле LINQ:

    Param_WindowWidth = InDB_SettingsSymbol.Where(x => x.Param_Name == "Param_WindowWidth").FirstOrDefault().Param_Value;
    

    Так же заметно обращения к методам классов «SE_Colors» и «SE_FillOC». Они находятся в папочке «Helpers», которую рассмотрим в следующем пункте.

    Хочу обратить внимание на одну особенность, обойти которую иначе я не придумал как. Связана она с размерами окна LongListSelector, в котором и падает матрица. Что б вывести символы в этом элементе управления в виде таблицы, необходимо использовать свойства LayoutMode=«Grid» и GridCellSize=«20, 20». К GridCellSize забиндиться не получилось.То есть размер каждой клеточки получился фиксированным. Но определить высоту и ширину этого элемента можно только после его загрузки, а ViewModel грузиться до загрузки UI. Поэтому при установке на гаджеты, у которых размер экрана отличный от 800*480 при первом запуске будет наблюдаться «урезоность» сетки матрицы внизу. Однако после перезапуска приложения уже будут браться правильные значения высоты и ширины из БД и все получается красиво. Если кто то знает как от этого избавиться динамическим способом (можно задать в БД значения, и исходя от размеров экрана брать нужный) — с радостью выслушаю.
    Ну а в коде этот кусок выглядит так:

            #region UpdateSettingsSymbolsByName
            /// <summary>
            /// ViewModel. Обновляем настройки таблицы Model_SettingsSymbols в БД по имени
            /// </summary>
            public void UpdateSettingsSymbolsByName(Model_SettingsSymbols SettingsSymbolsForDelete)
            {
                var query = (from Model_SettingsSymbols todo in db.DB_SettingsSymbol
                             where todo.Param_Name == SettingsSymbolsForDelete.Param_Name
                             select todo).SingleOrDefault();
    
                query.Param_Value = SettingsSymbolsForDelete.Param_Value;
    
                db.SubmitChanges();
            }
            #endregion
    

    И вызывается от события загрузки окна в MainPage.xaml.cs.

Папочка «Helpers»

  • Класс SE_Colors — помощник, ковертеры цвета из одного формата в другой. Например, что б хранить в БД цвет — нужно использовать string, а отправлять в объект ViewModel — SolidColorBrush, а для подсчета градиента нужно использовать перечислимый тип/список/тд., в моем случае это Dictionary. Может кому то будет полезным. Смотрим:
    SE_Colors

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Media;
    
    namespace SE_Matrix_2d_v_14.Helpers
    {
        public class SE_Colors
        {
            /// <summary>
            /// Конвертирует цвет из строки "#FF112233" в SolidColorBrush,
            /// поддерживается как с заданием компоненты А (8-9 знаков) так и без (6-7 знаков)
            /// </summary>
            /// <param name="color">Цвет заданный как строка ("#FF112233")</param>
            /// <returns>Возвращает цвет как объект SolidColorBrush</returns>
            public SolidColorBrush StringToBrush(string color)
            {
                color = color.Replace("#", "");
                
                switch(color.Length)
                {
                   case 8 :  
                        return new SolidColorBrush(Color.FromArgb(
                            byte.Parse(color.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
                            byte.Parse(color.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
                            byte.Parse(color.Substring(4, 2), System.Globalization.NumberStyles.HexNumber),
                            byte.Parse(color.Substring(6, 2), System.Globalization.NumberStyles.HexNumber) ));
                   case 6: 
                      return new SolidColorBrush(Color.FromArgb(
                          255,
                          byte.Parse(color.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
                          byte.Parse(color.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
                          byte.Parse(color.Substring(4, 2), System.Globalization.NumberStyles.HexNumber) ));
                  default: return null;
                }
            }
    
            /// <summary>
            /// Конвертирует цвет из объекта SolidColorBrush в ARGB и записывает это в Dictionary
            /// с соответствующими ключами
            /// </summary>
            /// <param name="color">Цвет, заданный как объект SolidColorBrush</param>
            /// <returns>Возвращает цвет, записанный в Dictionary</returns>
            public Dictionary<string, int> BrushToDictionary(SolidColorBrush color)
            {
                Dictionary<string, int> colorArgb = new Dictionary<string, int>();
    
                colorArgb["A"] = color.Color.A;
                colorArgb["R"] = color.Color.R;
                colorArgb["G"] = color.Color.G;
                colorArgb["B"] = color.Color.B;
    
                return colorArgb;
            }
    
            /// <summary>
            /// Формирует цвет из Dictionary в объект SolidColorBrush
            /// </summary>
            /// <param name="toBrush">Цвет, находящийся в Dictionary с соответствующими ключами ARGB</param>
            /// <returns>Возвращает цвет как объект SolidColorBrush</returns>
            public SolidColorBrush DictionaryToBrush(Dictionary<string, int> toBrush)
            {
                return new SolidColorBrush(new Color()
                {
                    A = (byte)toBrush["A"],
                    R = (byte)toBrush["R"],
                    G = (byte)toBrush["G"],
                    B = (byte)toBrush["B"]
                });
            }
    
            /// <summary>
            /// Конвертирует цвет, заданный строкой "#FF112233" в Dictionary с соответствующими ключами ARGB
            /// поддерживается как с заданием компоненты А (8-9 знаков) так и без (6-7 знаков)
            /// </summary>
            /// <param name="toDictionary">Цвет как строка "#FF112233"</param>
            /// <returns>Возвращает Dictionary с ключами ARGB</returns>
            public Dictionary<string, int> StringToDictionary(string toDictionary)
            {
                toDictionary = toDictionary.Replace("#", "");
                Dictionary<string, int> colorArgb = new Dictionary<string, int>();
    
                switch (toDictionary.Length)
                {
                    case 8:
                        colorArgb["A"] = byte.Parse(toDictionary.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
                        colorArgb["R"] = byte.Parse(toDictionary.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
                        colorArgb["G"] = byte.Parse(toDictionary.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
                        colorArgb["B"] = byte.Parse(toDictionary.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
                        break;
                    case 6:
                        colorArgb["A"] = 255;
                        colorArgb["R"] = byte.Parse(toDictionary.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
                        colorArgb["G"] = byte.Parse(toDictionary.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
                        colorArgb["B"] = byte.Parse(toDictionary.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
                        break;
                    default:
                        colorArgb["A"] = 255;
                        colorArgb["R"] = 255;
                        colorArgb["G"] = 255;
                        colorArgb["B"] = 255;
                        break;
                }
    
                return colorArgb;
            }
    
            /// <summary>
            /// Функция формирует коэффициент, который будет в последствии использоваться для создания градиента из
            /// падающих символов в матрице
            /// </summary>
            /// <param name="gradientFrom">Dictionary с ключами ARGB. Цвет для начала градиента, второй символ</param>
            /// <param name="gradientTo">Dictionary с ключами ARGB. Цвет для конца градиента, последний симол</param>
            /// <param name="count"></param>
            /// <returns>Dictionary с ключами ARGB и значениями коэффициента для каждого цвета</returns>
            public Dictionary<string, int> GetСoefficientFromGradient(Dictionary<string, int> gradientFrom, Dictionary<string, int> gradientTo, int count)
            {
                Dictionary<string, int> coefficientArgb = new Dictionary<string, int>();
    
                coefficientArgb["A"] = (int)Math.Round((gradientFrom["A"] - 10) / (double)(count + 1)) - 1;
                coefficientArgb["R"] = (int)Math.Round((gradientFrom["R"] - gradientTo["R"]) / (double)(count + 1)) - 1;
                coefficientArgb["G"] = (int)Math.Round((gradientFrom["G"] - gradientTo["G"]) / (double)(count + 1)) - 1;
                coefficientArgb["B"] = (int)Math.Round((gradientFrom["B"] - gradientTo["B"]) / (double)(count + 1)) - 1; 
    
                return coefficientArgb;
            }
    
            /// <summary>
            /// Формирует цвет каждого сивпола в змейке матрицы для создания градиента
            /// </summary>
            /// <param name="gradientFrom">Dictionary с ключами ARGB. Цвет для начала градиента, второй символ</param>
            /// <param name="coefficientFromGradient">Dictionary с ключами ARGB. Коэффициент для каждой компоненты цвета</param>
            /// <param name="coefficient">Коэффициент, расчитанный на основе порядкового номера симвоа в змейке матрицы</param>
            /// <returns>Цвет, как объект SolidColorBrush</returns>
            public SolidColorBrush GetSymbolColorForGradient(Dictionary<string, int> gradientFrom, Dictionary<string, int> coefficientFromGradient, int coefficient)
            {
                Dictionary<string, int> symbolColorForGradient = new Dictionary<string, int>();
    
                symbolColorForGradient["A"] = (gradientFrom["A"] - (coefficient * coefficientFromGradient["A"]));
                symbolColorForGradient["R"] = (gradientFrom["R"] - (coefficient * coefficientFromGradient["R"]));
                symbolColorForGradient["G"] = (gradientFrom["G"] - (coefficient * coefficientFromGradient["G"]));
                symbolColorForGradient["B"] = (gradientFrom["B"] - (coefficient * coefficientFromGradient["B"]));
    
                return DictionaryToBrush(symbolColorForGradient);
            }
        }
    }
    

  • Класс SE_FillOC — сокращение от Fill Observable Collection. Получаем данные из БД, а потом нужно передать их в объекты ViewModel. На каждую ObservableCollection по методу:
    SE_FillOC

    using SE_Matrix_2d_v_14.Models;
    using SE_Matrix_2d_v_14.Resources;
    using SE_Matrix_2d_v_14.ViewModels;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Media;
    
    namespace SE_Matrix_2d_v_14.Helpers
    {
        public class SE_FillOC
        {
            /// <summary>
            /// Заполняем ObservableCollection
            /// </summary>
            /// <param name="model">Вытяжка из БД с нужной таблицы</param>
            /// <returns>ObservableCollection</returns>
            public ObservableCollection<Model_SettingsSymbols> FillSettingsSymbols(IQueryable<Model_SettingsSymbols> model)
            {
                ObservableCollection<Model_SettingsSymbols> _itemObservableCollection = new ObservableCollection<Model_SettingsSymbols>();
                foreach(var toFill in model)
                {
                    _itemObservableCollection.Add(
                      new Model_SettingsSymbols {
                          ID = toFill.ID,
                          Param_Name = toFill.Param_Name,
                          Param_Value = toFill.Param_Value,
                          NameForTranslate = AppResources.ResourceManager.GetString(toFill.Param_Name, AppResources.Culture),
                      });
                }
                return _itemObservableCollection;
            }
    
            /// <summary>
            /// Заполняем ObservableCollection Model_Matrixes
            /// </summary>
            /// <param name="rowsNumber">Количество строк в матрице</param>
            /// <param name="columnsNumber">Количество столбцов в матрице</param>
            /// <returns>ObservableCollection</returns>
            public ObservableCollection<Model_Matrixes> FillMatrixes(int rowsNumber, int columnsNumber)
            {
                ObservableCollection<Model_Matrixes> _itemObservableCollection = new ObservableCollection<Model_Matrixes>();
               
                for (int rows = 0; rows < rowsNumber; rows++)
                {
                    for (int columns = 0; columns < columnsNumber; columns++)
                    {
                        _itemObservableCollection.Add(new Model_Matrixes()
                        {
                            Matrix_Name = columns + "_" + rows,
                            Matrix_Text = "",
                            Matrix_FontSize = 15,
                            Matrix_Foreground = new SolidColorBrush(Colors.Cyan)
                        });
                    }
                }
                return _itemObservableCollection;
            }
    
            /// <summary>
            /// Заполняем ObservableCollection
            /// </summary>
            /// <param name="model">Вытяжка из БД с нужной таблицы</param>
            /// <returns>ObservableCollection</returns>
            public ObservableCollection<Model_Colors> FillColors(IQueryable<Model_Colors> model)
            {
                ObservableCollection<Model_Colors> _itemObservableCollection = new ObservableCollection<Model_Colors>();
    
                foreach (var toFill in model)
                {
                    _itemObservableCollection.Add(
                      new Model_Colors
                      {
                          ID = toFill.ID,
                          Name = toFill.Name,
                          Value = toFill.Value,
                          NameForTranslate = AppResources.ResourceManager.GetString(toFill.Name, AppResources.Culture),
                      });
                }
                return _itemObservableCollection;
            }
    
            /// <summary>
            /// Заполняем ObservableCollection
            /// </summary>
            /// <param name="model">Вытяжка из БД с нужной таблицы</param>
            /// <returns>ObservableCollection</returns>
            public ObservableCollection<Module_Languages> FillLanguages(IQueryable<Module_Languages> model)
            {
                ObservableCollection<Module_Languages> _itemObservableCollection = new ObservableCollection<Module_Languages>();
    
                foreach (var toFill in model)
                {
                    _itemObservableCollection.Add(
                      new Module_Languages
                      {
                          ID = toFill.ID,
                          Name = toFill.Name,
                          ValueFrom = toFill.ValueFrom,
                          ValueTo = toFill.ValueTo,
                          NameForTranslate = AppResources.ResourceManager.GetString(toFill.Name, AppResources.Culture),
                          Selected = toFill.Selected
                      });
                }
                return _itemObservableCollection;
            }
        }
    }
    

MainPage.xaml.cs

Ничего нового. В основном только события нажатия или потери фокуса.

MainPage.xaml.cs

using System;
using System.Net;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using SE_Matrix_2d_v_14.Models;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Media;

namespace SE_Matrix_2d_v_14
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Конструктор
        public MainPage()
        {
            InitializeComponent();

            // Задайте для контекста данных элемента управления listbox пример данных
            this.DataContext = App.ViewModel;
        }

        // Загрузка данных для элементов ViewModel
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (!App.ViewModel.IsDataLoaded)
            {
                App.ViewModel.LoadData();
            }
            App.ViewModel.SaveChangesToDB();
        }


        private bool _updateSettings = false;
        public bool UpdateSettings
        {
            get { return _updateSettings; }
            set { _updateSettings = value; }
        }

        /// <summary>
        /// Если текст в поле изменился и его длина больше 0, то меняем флаг на true, что
        /// при срабатывании события потери фокуса позволит изменить данные в модели и БД
        /// </summary>
        private void Event_TextBox_TextChanged_SettingSymbols(object sender, TextChangedEventArgs e)
        {
            TextBox paramToUpdate = sender as TextBox;

            if (paramToUpdate.Text.Length > 0)
            {
                UpdateSettings = true;
            }
        }

        /// <summary>
        /// Если UpdateSettings == true, то меняем данные в модели и БД на те, что ввели
        /// </summary>
        private void Event_TextBox_LostFocus_SettingsSymbols(object sender, RoutedEventArgs e)
        {
            TextBox paramToUpdate = sender as TextBox;

            if (UpdateSettings == true)
            {
                UpdateSettings = false;
                Model_SettingsSymbols updateInTable = new Model_SettingsSymbols
                {
                    ID = Convert.ToInt32(paramToUpdate.Tag),
                    Param_Value = Convert.ToInt32(paramToUpdate.Text)
                };
                App.ViewModel.UpdateSettingsSymbolsByID(updateInTable);               
            }
        }

        /// <summary>
        /// При завершении загрузки обновляем размеры поля, где выводится матрица
        /// </summary>
        private void Event_LongListSelector_OnLoaded_Matrix(object sender, RoutedEventArgs e)
        {
            LongListSelector paramToUpdate = sender as LongListSelector;

            Model_SettingsSymbols updateHeightInTable = new Model_SettingsSymbols
            {
                Param_Name = "Param_WindowHeight",
                Param_Value = Convert.ToInt32(paramToUpdate.ActualHeight)
            };
            App.ViewModel.UpdateSettingsSymbolsByName(updateHeightInTable);

            Model_SettingsSymbols updateWigthInTable = new Model_SettingsSymbols
            {
                Param_Name = "Param_WindowWidth",
                Param_Value = Convert.ToInt32(paramToUpdate.ActualWidth)
            };
            App.ViewModel.UpdateSettingsSymbolsByName(updateWigthInTable);
        }

        /// <summary>
        /// При нажатии на область, где выводится матрица - добавляем заданное ранее в настройках новое количество змеек.
        /// </summary>
        private async void Event_LongListSelector_Tap_StartMatrix(object sender, System.Windows.Input.GestureEventArgs e)
        {
             App.ViewModel.Start();
        }

        /// <summary>
        /// После выбора цвета и нажатия на соответствующий прямоугольник - сохраняем новый цвет в БД и обновляем модель
        /// </summary>
        private void Event_Rectangle_Tap_ChangeColor(object sender, System.Windows.Input.GestureEventArgs e)
        {
            Rectangle paramToUpdate = sender as Rectangle;

            Model_Colors updateInTable = new Model_Colors
            {
                ID = Convert.ToInt32(paramToUpdate.Tag),
                Value = ColorPicker.Color.ToString()
            };
            App.ViewModel.UpdateSettingsColorByID(updateInTable);
        }

        /// <summary>
        /// После выбора языка меняем данные в модели и БД
        /// </summary>
        private void Event_ListPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            Module_Languages selectedItem = ((sender as ListPicker).SelectedItem as Module_Languages);

            Module_Languages updateInTable = new Module_Languages
            {
                ID = selectedItem.ID
            };
            App.ViewModel.UpdateSettingsColorByID(updateInTable);
        }
    }
}

Выводы

Количество кода увеличилось, однако увеличилась читаемость и расширяемость. MVVM намного более удобен в данном случае. Однако производительность приложения (оценивал в максимальном количестве змеек в матрице, при котором нет притормаживаний) несколько снизилась.

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

Автор: struggleendlessly

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/mvvm/48581

Ссылки в тексте:

[1] Github: https://github.com/struggleendlessly/SE-Matrix-MVVM/tree/SE/SE_Matrix_2d_v_14

[2] Windows Phone Store: http://www.windowsphone.com/ru-ru/store/app/se-matrix/a2b8957e-cf16-4dec-8b06-5d591b351893?signin=true

[3] Windows Phone 8: Создаем приложение. Матрица. Часть 1: http://habrahabr.ru/post/195422/

[4] Windows Phone 8: Создаем приложение. Матрица. Часть 2: http://habrahabr.ru/post/195760/

[5] Windows Phone 8: Создаем приложение. Матрица. Часть 3. MVVM: http://habrahabr.ru/post/201240/

[6] INotifyPropertyChanged: http://msdn.microsoft.com/ru-ru/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx

[7] INotifyPropertyChanging: http://msdn.microsoft.com/ru-ru/library/system.componentmodel.inotifypropertychanging(v=vs.110).aspx

[8] DataContext: http://msdn.microsoft.com/ru-ru/library/system.data.linq.datacontext(v=vs.110).aspx