|
При выполнении в лабораторной работе упражнения №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 ). Это тоже существенно замедлит работу.
Базы данных - это большое достижение 'всего прогрессивного человечества' (и нас с вами!): они универсальны, надежны, элегантны, незаменимы, а программировать их просто приятно и 'просто просто' (это элементарно, Ватсон).


