При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Microsoft.Xna.Framework.Graphics;
namespace Application1 { public partial class MainForm : Form { // Объявим поле графического устройства для видимости в методах GraphicsDevice device;
public MainForm() { InitializeComponent();
// Подпишемся на событие Load формы this.Load += new EventHandler(MainForm_Load);
// Попишемся на событие FormClosed формы this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed); }
void MainForm_FormClosed(object sender, FormClosedEventArgs e) { // Удаляем (освобождаем) устройство device.Dispose(); // На всякий случай присваиваем ссылке на устройство значение null device = null; }
void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства PresentationParameters presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height;
// Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); }
protected override void OnPaint(PaintEventArgs e) { device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
base.OnPaint(e); } } } Выбрасывается исключение: Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл. Делаю все пунктуально. В чем может быть проблема? |
Работа с потоками данных
Пример 5. Совместное применение файловой библиотеки рисунков и базы данных
Для отображения упакованных в предыдущем примере рисунков применим отдельное окно WPF, подобное Window3.xaml, но отличающееся процедурной частью.
- Закройте все текущие файлы командой Window/Close All Documents
- В панели проводнике решений Solution Explorer вызовите контекстное меню для файла Window3.xaml и выполните команду Copy
- Там же, вызовите контекстное меню для узла App7 самого проекта и выполните команду Paste
- Там же, командой Rename переименуйте копию Copy of Window3.xaml в Window5.xaml
- Откройте файл Window5.xaml, замените в нем заголовок окна и индекс 3 на 5, чтобы разметка стала такой
<Window x:Class="App7.Window5" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Использование БД + файловой библиотеки" Height="300" Width="470" MinHeight="300" MinWidth="450" Name="_Window5" Closing="Window_Closing" > <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="100" /> </Grid.ColumnDefinitions> <Border Background="Black"> <Image Name="image" Stretch="Uniform" /> </Border> <ListBox Name="listBox" Grid.Column="1" Padding="5,0" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="listBox_SelectionChanged"> </ListBox> </Grid> </Window>
- Удалите из файла Window5.xaml.cs ненужный пока процедурный код и добавьте в начало подключение пространств имен ADO.NET, чтобы осталась следующая заготовка, которую мы дальше и будем наполнять
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; // Дополнительные пространства имен using FORMS = System.Windows.Forms; // Псевдоним using System.IO; using System.Configuration; // Для SettingsProperty using IO = System.IO; // Псевдоним для адресации Path // Дополнительные пространства имен для ADO.NET using System.Data; using System.Data.OleDb; using System.Data.Common; namespace App7 { public partial class Window5 : Window { FileStream fs = null; // Поле файлового потока для видимости в классе String nameLibrary; // Опорное поле для свойства с именем библиотеки int count; // Поле под число рисунков string[] names; // Поле под имена рисунков Properties.Settings settings; // Параметры // Конструктор public Window5() { InitializeComponent(); // Запускаем диалог выбора библиотеки nameLibrary = SelectLibrary(); // При отказе от выбора ничего не запускаем, // а в главном окне Window1 закрываем это окно if (nameLibrary != String.Empty) ExecuteWindow(); } // Извлекаем рисунки и управляем показом private void ExecuteWindow() { } // При смене индекса списка показывает выбранный рисунок private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { } // Свойство доступа, проверяется в Window1 public String NameLibrary { get { return nameLibrary; } } // Диалог выбора библиотеки. Возвращает // пусто, если библиотека не выбрана private String SelectLibrary() { settings = new App7.Properties.Settings(); String path = settings.dialogOpen; String fileName = IO.Path.Combine(path, "Pictures.my1.lib"); // Создаем и настраиваем диалог выбора файла FORMS.OpenFileDialog openFileDialog = new FORMS.OpenFileDialog(); openFileDialog.InitialDirectory = path; openFileDialog.FileName = ""; openFileDialog.Filter = "Library Files(*.my1.lib)|*.my1.lib"; FORMS.DialogResult result = openFileDialog.ShowDialog(); if (result != FORMS.DialogResult.Cancel) fileName = openFileDialog.FileName; // Полное имя else fileName = String.Empty; return fileName; } // Обработчик события Closing, срабатывает перед закрытием приложения private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { // Закрываем библиотечный поток if (fs != null) fs.Close(); // Сохраняем параметры if (nameLibrary != String.Empty) { settings.dialogOpen = nameLibrary.Substring(0, nameLibrary.LastIndexOf('\\')); settings.Save(); } } } }
- В файле Window1.xaml измените заголовок списка для ' Пример 5 ' на ' Пример 5. Использование БД + файловой библиотеки '
- Заполните в файле Window1.xaml.cs функцию Example5() следующим кодом (окончательный, приводится полностью)
void Example5() { // Предотвращение повторного создания дочернего окна bool existsWindow = false; foreach (Window window in Application.Current.Windows)// Перебираем коллекцию { if (window.Name == "_Window5") { // Нашли среди существующих existsWindow = true; window.Activate();// Сдвинуть на передний план break;// Прервать перебор } } if (!existsWindow)// Если не существует, то создать и показать { Window5 wnd = new Window5(); // Перед показом проверяем, выбрана ли библиотека if (wnd.NameLibrary != String.Empty) wnd.Show(); // Показываем окно Window5 else wnd.Close(); // Закрываем окно Window5 } }
- Запустите приложение - окно Window5 запускается, но пока не функционирует
- Добавьте в файл Window5.xaml.cs к членам класса Window5 функцию, возвращающую строку соединения с БД
public partial class Window5 : Window { ........................................................ // Строка соединения с БД private String ConnectionString(String fileName) { //string pathToFile = Application.StartupPath.ToString(); string JetEngineString = "Provider=Microsoft.Jet.OLEDB.4.0;" + "Jet OLEDB:Engine Type=5;Data Source="; return JetEngineString + fileName; } ........................................................ }
- Заполните функцию ExecuteWindow() следующим кодом
// Извлекаем рисунки и управляем показом private void ExecuteWindow() { // Формируем имя БД из имени файловой библиотеки string nameDB = nameLibrary.Substring(0, nameLibrary.LastIndexOf('.')) + ".mdb"; // Читаем некоторые данные из БД OleDbConnection connection = new OleDbConnection(ConnectionString(nameDB)); OleDbCommand command = new OleDbCommand("SELECT COUNT(*) FROM MyTable"); command.Connection = connection; connection.Open(); // Извлекаем число рисунков count = (int)command.ExecuteScalar(); // Меняем команду и извлекаем имена рисунков command.CommandText = "SELECT FileName FROM MyTable"; OleDbDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection); names = new string[count]; int i = 0; foreach (DbDataRecord rec in reader)// Равносильно while(dataReader.Read()) names[i++] = ((string)rec[0]).Trim();// Сразу убираем пробелы // Соединение здесь закроет сам объект DataReader после прочтения всех данных // в соответствии с соглашением при его создании CommandBehavior.CloseConnection // Привязываем массив имен рисунков к списку listBox.ItemsSource = names; // Выделяем первый элемент списка, чтобы вызвать SelectionChanged listBox.SelectedIndex = 0; listBox.Focus(); }
- Разберитесь с кодом метода ExecuteWindow() и запустите пример - результат для ранее созданной БД Pictures.my1.mdb пока будет таким
Список с именами, полученными из БД Pictures.my1.mdb, функционирует нормально, но сами рисунки еще нужно извлечь из библиотеки Pictures.my1.lib и присоединить к элементу отображения Image.
- Заполните заготовку обработчика listBox_SelectionChanged() следующим кодом
// При смене индекса списка показывает выбранный рисунок private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { // Формируем имя БД из имени файловой библиотеки string nameDB = nameLibrary.Substring(0, nameLibrary.LastIndexOf('.')) + ".mdb"; // Создаем и настраиваем объект соединения с БД OleDbConnection connection = new OleDbConnection(); connection.ConnectionString = ConnectionString(nameDB); // Создаем и настраиваем объект команды, параметризованной по имени рисунка OleDbCommand command = new OleDbCommand(); command.Connection = connection; command.CommandType = CommandType.Text; // Необязательно! Установлено по умолчанию command.CommandText = "SELECT Offset, Length FROM MyTable WHERE FileName=?"; command.Parameters.Add(new OleDbParameter()); command.Parameters[0].Value = ((ListBox)sender).SelectedValue.ToString();// Имя рисунка // Извлекаем данные из БД connection.Open(); OleDbDataReader reader = command.ExecuteReader(); reader.Read(); // Читаем только одну строку (она и будет всего одна) long offset = (int)reader["Offset"]; int length = (int)reader["Length"]; connection.Close(); // Раньше этого места закрывать нельзя! // Создаем и открываем один раз файловый поток библиотеки с рисункам if (fs == null) fs = new FileStream( nameLibrary, FileMode.Open, FileAccess.Read, FileShare.Read); // Извлекаем рисунок из потока byte[] bytes = new byte[length]; fs.Seek(offset, SeekOrigin.Begin); for (int i = 0; i < length; i++) bytes[i] = (byte)fs.ReadByte(); // Отображаем рисунок пользователю BitmapImage bi = new BitmapImage(); bi.BeginInit(); bi.StreamSource = new MemoryStream(bytes); bi.EndInit(); image.Source = bi; }
- Разберитесь с кодом обработчика и запустите пример - результат для ранее созданной БД Pictures.my1.mdb и библиотеки Pictures.my1.lib соответствует ожидаемому
Пример 6. Создание базы данных OLE DB и заполнение ее рисунками
Если уж мы ранее решили использовать базу данных для управления рисунками, то более конструктивным будет подход, когда сами рисунки тоже хранятся в базе данных. Чтобы его реализовать, по прежнему нужно разработать две утилиты: одна создает БД и заполняет ее рисунками из выбранного каталога; вторая извлекает эти рисунки из БД и показывает пользователю. В данном примере мы создадим первую утилиту (правда, без потоков), а в следующем - вторую (с потоком). Итак, приступим...
Начнем с создания реляционной БД программным способом. База данных будет иметь имя Pictures.my2.md b и содержать одну таблицу с именем MyTable, структура которой в типах OLE D B и объектной модели C # будет такой
Имя поля | Тип OLE DB | Тип объектной модели C# | Размер |
---|---|---|---|
Name | Type | Type | DefinedSize |
FileName (уникальное) | adWChar | string | 20 (определим сами, с запасом) |
Picture | adLongVarBinary (BLOB) | byte[] | 0 (неопределено) |
- В файле Window1.xaml измените заголовок списка для элемента ' Пример 6 ' на ' Пример 6. Создание базы данных с рисунками '
- В панели Solution Explorer добавьте к узлу App7 командой контекстного меню Add/Class новый файл с именем CreateDB.cs и заполните его следующим кодом (приводится полностью)
using System; using System.Windows.Forms; using System.IO; // Пространства имен для ADO.NET using System.Data; using System.Data.OleDb; using System.Data.Common; namespace App7 { class CreateDB { // Локальные поля String nameDB = "Pictures.my2.mdb"; string path, fileName, tableName = "MyTable";// Нельзя давать имя "Local"!!! String extensionPictures = "jpg"; // Параметризованный конструктор public CreateDB(string path) { // Инициализируем поля this.path = path; fileName = Path.Combine(path, nameDB); // Удалим предыдущую базу данных, если она есть if (new FileInfo(fileName).Exists) File.Delete(fileName); } // Строка соединения с БД private String ConnectionString(String fileName) { //string pathToFile = Application.StartupPath.ToString(); string JetEngineString = "Provider=Microsoft.Jet.OLEDB.4.0;" + "Jet OLEDB:Engine Type=5;Data Source="; return JetEngineString + fileName; } // Создание БД public bool CreateDatabase() { Object adoConnection = null; ADOX.CatalogClass catalog = new ADOX.CatalogClass(); try { catalog.Create(ConnectionString(fileName)); adoConnection = catalog.ActiveConnection; } catch (Exception ex) { MessageBox.Show(String.Format("Ошибка в создании БД\n{0}", ex.Message), "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } finally { if (catalog != null && adoConnection != null) { ((ADODB.Connection)adoConnection).Close();// Приводим к интерфейсу /*********** Вариант ********* adoConnection.GetType().InvokeMember( "Close", System.Reflection.BindingFlags.InvokeMethod, null, adoConnection, new object[0]); //*****************************/ catalog = null; adoConnection = null; } } return true;// Успех, если дошли до этого места } // Создание таблицы public bool CreateTable() { ADODB.Connection conn = new ADODB.ConnectionClass(); conn.Open(ConnectionString(fileName), null, null, 0); ADOX.Catalog catalog = new ADOX.CatalogClass(); catalog.ActiveConnection = conn; try { ADOX.TableClass table = new ADOX.TableClass();// Объект таблицы table.ParentCatalog = catalog; // Указали родителя для таблицы table.Name = tableName; // Имя таблицы catalog.Tables.Append(table); // Добавили в коллекцию таблиц // Задаем параметры настройки таблицы string[] columnNames = { "FileName", "Picture" }; // Неопределенный размер заменяем нулем int[] columnSizes = new int[] { 20, 0 }; ADOX.DataTypeEnum[] columnTypes = new ADOX.DataTypeEnum[] { ADOX.DataTypeEnum.adWChar, // До 255 символов fixed ADOX.DataTypeEnum.adLongVarBinary // BLOB }; // Настраиваем структуру таблицы ADOX.ColumnClass column; for (int i = 0; i < columnSizes.Length; i++) { column = new ADOX.ColumnClass(); column.ParentCatalog = catalog; // Указали родителя для столбца column.Name = columnNames[i]; column.DefinedSize = columnSizes[i]; column.Type = columnTypes[i]; column.Attributes = ADOX.ColumnAttributesEnum.adColNullable;// Позволить пусто //column.Attributes = ADOX.ColumnAttributesEnum.adColFixed;// Фиксированная длина table.Columns.Append(column, column.Type, column.DefinedSize); } // Делаем первый столбец уникальным (совпадения не допускаются) // http://www.sqldev.org/sql-server-data-access/ // multi-column-primary-keys-in-access-using-adox-c-60766.shtml ADOX.Table tbl = catalog.Tables[tableName]; tbl.Keys.Append( "UniqueKeyLocal", ADOX.KeyTypeEnum.adKeyUnique, "FileName", null, null); } catch (Exception ex) { MessageBox.Show(ex.Message.ToString()); return false; } finally { conn.Close();// Приводим к интерфейсу } return true;// Успех, если дошли до этого места } // Наполняем файловую БД данными public bool CreateData() { // Читаем информацию о всех файлах выбранного каталога FileInfo[] filesInfo = new DirectoryInfo(path). GetFiles("*." + extensionPictures); if (filesInfo.Length == 0) { MessageBox.Show( "В выбранном каталоге нет файлов для упаковки в БД", "Предупреждение", MessageBoxButtons.OK, MessageBoxIcon.Information); return false; } // Сортируем объекты массива FileInfo по именам файлов Array.Sort<FileInfo>(filesInfo, delegate(FileInfo x, FileInfo y)// Анонимный делегат вместо функции обратного вызова { return String.Compare(x.Name, y.Name); }); // Создаем и настраиваем инфраструктуру ADO.NET OleDbConnection conn = new OleDbConnection(ConnectionString(fileName)); OleDbCommand cmd = new OleDbCommand(); cmd.CommandType = CommandType.Text; // Необязательно! cmd.CommandText = "SELECT * FROM " + tableName; cmd.Connection = conn; OleDbDataAdapter adapter = new OleDbDataAdapter(); adapter.SelectCommand = cmd; new OleDbCommandBuilder(adapter); // Донастраиваем адаптер на запись // Пишем построчно в объектную таблицу и затем сразу в БД, // чтобы экономить оперативную память, если рисунков много DataTable table = null; conn.Open();// Метод FillSchema() теперь уже не закроет!!! for (int i = 0; i < filesInfo.Length; i++) { // Создаем и настраиваем объектную модель таблицы if (table == null) { // Только один раз table = new DataTable(tableName); adapter.FillSchema(table, SchemaType.Source); } else table.Clear(); // Теперь только чистим, структура таблицы остается // Получаем сокращенное имя файла без пути и расширения String shortName = filesInfo[i].Name; // Уже без пути и есть!!! int len = shortName.LastIndexOf('.'); shortName = shortName.Substring(0, len);// Убрали расширение // Добавляем в строку имя рисунка (и порядковый номер, чтобы було!) DataRow row = table.NewRow(); row["FileName"] = (i + 1).ToString() + ") " + shortName; // Читаем файл с рисунком byte[] bytes = File.ReadAllBytes( Path.Combine(filesInfo[i].DirectoryName, filesInfo[i].Name)); /*********** Вариант ********* FileStream fileStream = filesInfo[i].OpenRead(); byte[] bytes = new byte[fileStream.Length]; int readByte, count = 0; while ((readByte = fileStream.ReadByte()) != -1) bytes[count++] = (byte)readByte; fileStream.Close(); //*****************************/ // Добавляем в строку сам рисунок, адресуясь по имени столбца row["Picture"] = bytes; table.Rows.Add(row); // Скидываем в объектную таблицу adapter.Update(table); // Записываем эту одну строку в БД } // Удаляем объектную таблицу (Необязательно!) table.Dispose(); // Закрываем соединение conn.Close(); return true;// Успех, если дошли до этого места } } }
Учитывая, что открытие соединения с БД является очень медленной операцией, в методе CreateData() перед вхождением в цикл заполнения БД рисунками мы открываем соединение только один раз. Когда соединение открыто, то методы адаптера FillSchema() и Fill() его не закрывают, поэтому после цикла нужно его закрыть самим. Экземпляр объекта построителя команд OleDbCommandBuilder нам нужен только для настройки адаптера на обновление данных в БД (по умолчанию последний настроен только на чтение), для этого нет необходимости создавать на него ссылку.
Желательно, чтобы рисунки попадали в БД уже отсортированными по именам. Поэтому после получения из выбранного каталога всех объектов FileInfo, мы их предварительно сортируем и только потом считываем с диска файлы в установленном порядке. Для сортировки используем перегрузку типизированного статического метода с делегатом, чтобы не выносить отдельно другую статическую функцию - обратного вызова (компаратор). В ходе сортировки метод Sort() автоматически будет обращаться к этой функции (анонимному делегату) при оценке весов Name очередных сравниваемых пар элементов массива.
- В файле Window1.xaml.cs найдите функцию Example6() и заполните ее следующим вызывающим кодом (приводится полностью)
void Example6() { // Диалог выбора каталога dialogFolder = new System.Windows.Forms.FolderBrowserDialog(); dialogFolder.Description = "Выберите каталог для создания БД"; dialogFolder.ShowNewFolderButton = false;// Отключить кнопку создания новой папки dialogFolder.RootFolder = Environment.SpecialFolder.MyComputer;// Компьютер dialogFolder.SelectedPath = settings.dialogFolder; System.Windows.Forms.DialogResult result = dialogFolder.ShowDialog(); // Если был отказ или диалог закрыт системной кнопкой if (result == System.Windows.Forms.DialogResult.Cancel) return; // Извлекаем новый каталог String path = dialogFolder.SelectedPath;// Полный путь // Создаем структуру БД CreateDB create = new CreateDB(path); bool success; if (success = create.CreateDatabase()) success = create.CreateTable(); if (!success) { MessageBox.Show("Структура БД не создана"); return; } success = create.CreateData(); if (success) MessageBox.Show("База данных создана"); else MessageBox.Show("База данных не создана"); // Принудительная перерисовка окна WPF this.Height = this.ActualHeight + 1; this.Height = this.ActualHeight - 1; }
- Запустите пример и зайдите в папку Images, расположенную в прилагаемом к работе каталоге Source
Мы видим, что файловая БД Pictures.my2.mdb создается и наполняется данными. Если открыть эту БД в Office Access, то она (с учетом автоматической сортировки Office Access ) будет такой
Если проверить разработанную утилиту на создании БД с большим количеством рисунков (1000), то в сравнении с формированием файловой библиотеки она работает ничуть не медленнее. Основная выгода заключается в том, что при записи каждого рисунка мы пользуемся уже открытым соединением. Точно также для библиотечного файла поток создается только один раз. Мы дальше убедимся, что при чтении файловой библиотеки и БД скорость тоже мало различима (и это хорошо), поскольку там и там извлекается только один рисунок и скорость открытия примерно равна.
Можно было бы, конечно, сначала прочитать все рисунки с диска, а потом сразу записать их в БД, и это было бы еще быстрее. Но рисунков может оказаться много и большого размера. В этом случае оперативной памяти компьютера просто нехватит и он вынужден будет создавать на жестком диске временные файлы промежуточного хранения данных (такой механизм называется спуллинг - Simultaneous Peripherial Operation Off Line ). Это тоже существенно замедлит работу.
Базы данных - это большое достижение 'всего прогрессивного человечества' (и нас с вами!): они универсальны, надежны, элегантны, незаменимы, а программировать их просто приятно и 'просто просто' (это элементарно, Ватсон).