Все необходимые для выполнения данной работы программы можно найти в Setup.zip.
В WPF предусмотрены два вида пользовательских элементов:
Настраиваемые пользовательские элементы невидимы. Для них проектная плоскость Design с возможностью перетаскивания элементов из панели Toolbox не предусмотрена, поэтому надо создавать шаблон. А пользовательские элементы управления имеют проектную плоскость времени проектирования и их можно создавать точно также, как проект интерфейса приложения WPF Application. Но запускать на выполнение пользовательские элементы управления можно только с помощью исполнимого теста.
Поставим задачу разработать секундомер, показывающий время, оставшееся до наступления какого-нибудь события. Объект таймера оформим как пользовательский элемент управления, помещенный в библиотеку типа WPF User Control Library. При построении используем механизмы ресурсов и привязки WPF.
Создайте решение ClockAndConvertHTML и новый проект библиотеки WpfControlLibrary, используя шаблон WPF User Control Library в типе проектов Visual Studio
В Solution Explorer переименуйте автоматически сгенерированный файл UserControl1.xaml в Clock.xaml
В редакторе файла Clock.xaml на вкладке XAML жестом Ctrl+H вызовите окно замены с настройкой Current Project и замените кнопкой Replace All все вхождения UserControl1 на Clock
В панели Solution Explorer вызовите контекстное меню для корневого узла WpfControlLibrary проекта и добавьте папку Code командой Add/New Folder
В панели Solution Explorer вызовите контекстное меню для папки Code и командой Add/Class добавьте в нее файл с именем DigitalClock.cs, который заполните такusing System;
using System.Windows; // DependencyObject, DependencyProperty
using System.ComponentModel; // INotifyPropertyChanged
using System.Windows.Threading; // DispatcherTimer
using System.Globalization; // CultureInfo
using System.Windows.Controls; // TextBlock
using System.Windows.Media; // FontFamily
namespace MyClock
{
public class DigitalClock : TextBlock, INotifyPropertyChanged
{
const int COUNT_TIME = 10; // Минуты
// Событие реализации интерфейса INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
// Обычное событие остановки таймера
public event EventHandler StopDigitalClock;
// Открытое свойство
int minutes, seconds, time;
public TextBlock Time
{
get
{
minutes = time / 60;
seconds = time % 60;
this.Text = String.Format("{0:00}:{1:D2}", minutes, seconds);
if (time > 0)
time -= 1;
return this;
}
}
// Конструктор запускает таймер
DispatcherTimer timer;
public DigitalClock()
{
// Настраиваем дизайн
this.FontFamily = new FontFamily("Arial");
this.FontSize = 1.3333 * 36; // 36pt
this.FontWeight = FontWeights.Bold;
this.Background = Brushes.Red;
this.Foreground = Brushes.Yellow;
this.Padding = new Thickness(5, 0, 5, 0);
// Создаем таймер
timer = new DispatcherTimer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = TimeSpan.FromSeconds(1);
// Начальное значение
time = COUNT_TIME * 60; // Секунды
}
public void Start()
{
timer.Stop();
timer.Start();
}
public void Restart()
{
time = COUNT_TIME * 60; // Секунды
timer.Stop();
timer.Start();
}
public void StopResult()
{
timer.Stop();
if (StopDigitalClock != null)
StopDigitalClock(this, EventArgs.Empty);
}
public void Stop()
{
timer.Stop();
}
// Обработчик события таймера возбуждает свойство Time
// и генерирует обычное событие останова
void timer_Tick(object sender, EventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Time"));
if (minutes <= 0 seconds <= 0)
{
timer.Stop();
if (StopDigitalClock != null)
StopDigitalClock(this, EventArgs.Empty);
}
}
}
}
Класс возбуждает обычное событие StopDigitalClock, когда заканчивается лимит времени.
Откройте на редактирование файл Clock.xaml в представлении XAML и заполните его следующей разметкой<UserControl x:Class="WpfControlLibrary.Clock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyClock"
>
<UserControl.Resources>
<local:DigitalClock x:Key="clock" StopDigitalClock="DigitalClock_StopDigitalClock" />
</UserControl.Resources>
<Label Content="{Binding Source={StaticResource clock}, Path=Time}"
BorderBrush="Blue" BorderThickness="5" Padding="0"
HorizontalAlignment="Center" />
</UserControl>
Откройте на редактирование файл Clock.xaml.cs и заполните его следующим процедурным кодом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.Navigation;
using System.Windows.Shapes;
using MyClock; // Пространство имен таймера в библиотеке WpfControlLibrary
namespace WpfControlLibrary
{
public partial class Clock : UserControl
{
public Clock()
{
InitializeComponent();
// Извлекаем объект из коллекции ресурсов
clock = this.Resources["clock"] as DigitalClock;
}
DigitalClock clock; // Ссылка на экземпляр из ресурсов
// Поле маршрутизированного события
public static readonly RoutedEvent StopClockEvent;
// Статический конструктор
static Clock()
{
// Регистрация маршрутизированного события
StopClockEvent = EventManager.RegisterRoutedEvent(
"StopClock", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(Clock));
}
// Контейнер для подписки на маршрутизированное событие
public event RoutedEventHandler StopClock
{
add { AddHandler(StopClockEvent, value); }
remove { RemoveHandler(StopClockEvent, value); }
}
// Возбуждение маршрутизированного события через обработчик простого события
private void DigitalClock_StopDigitalClock(object sender, EventArgs e)
{
this.RaiseEvent(new RoutedEventArgs(Clock.StopClockEvent));
}
// Функции доступа для управления таймером из клиента
public void Start()
{
clock.Start();
}
public void Restart()
{
clock.Restart();
}
public void StopResult()
{
clock.StopResult();
}
public void Stop()
{
clock.Stop();
}
}
}
Обычное событие StopDigitalClock через обработчик возбуждает всплывающее событие StopClock, которое в клиенте можно будет слушать и управлять интерфейсом. Для управления таймером в классе Clock предусмотрены функции доступа.
Выполните команду меню Build/Build Solution или жест F6, чтобы создать библиотечную сборку
В панели Solution Explorer щелкните на пиктограмме Show All Files и убедитесь, что сборка WpfControlLibrary.dll действительно создана
Проверим работу
Командой меню оболочки File/Add/New Project добавьте к решению исполнимую сборку типа WPF Application с именем TestClock
В панели Solution Explorer вызовите контекстное меню для узла TestClock и командой Set as StartUp Project назначьте его стартовым. Обратите внимание, что после выполнение этой команды узел
В Solution Explorer выполните для узла References проекта TestClock команду Add Reference контекстного меню и на вкладке Projects подключите библиотечную сборку WpfControlLibrary.dll к тестовому проекту
В Solution Explorer выделите любой узел тестового проекта TestClock, щелкните на пиктограмме Show All Files и раскройте узел bin\Debug - убедитесь, что пока он пустой
Откомпилируйте тестовый проект и щелкните в Solution Explorer на пиктограмме Refresh - убедитесь, что оболочка скопировала в каталог размещения тестовой сборки нашу библиотечную сборку WpfControlLibrary.dll
Откройте на редактирование файл Window1.xaml в представлении XAML и заполните его следующей разметкой<Window x:Class="TestClock.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lib="clr-namespace:WpfControlLibrary;assembly=WpfControlLibrary"
Title="TestClock" Height="300" Width="300"
lib:Clock.StopClock="Window_StopClock"
>
<StackPanel lib:Clock.StopClock="StackPanel_StopClock">
<lib:Clock Name="timer" />
<Button Click="start_Click">Start</Button>
<Button Click="restart_Click">Restart</Button>
<Button Click="stopResult_Click">StopResult</Button>
<Button Click="stop_Click">Stop</Button>
</StackPanel>
</Window>
Заполните файл Window1.xaml.cs следующим процедурным кодом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.Navigation;
using System.Windows.Shapes;
namespace TestClock
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void start_Click(object sender, RoutedEventArgs e)
{
timer.Start();
}
private void restart_Click(object sender, RoutedEventArgs e)
{
timer.Restart();
}
private void stopResult_Click(object sender, RoutedEventArgs e)
{
timer.StopResult();
}
private void stop_Click(object sender, RoutedEventArgs e)
{
timer.Stop();
}
private void Window_StopClock(object sender, RoutedEventArgs e)
{
MessageBox.Show("StopClock", "Window1");
}
private void StackPanel_StopClock(object sender, RoutedEventArgs e)
{
MessageBox.Show("StopClock", "StackPanel");
}
}
}
На прослушивание всплывающего события StopClock мы в разметке подписали два интерфейсных элемента: Window и StackPanel, которые в своих обработчиках выдают диалоговое окно MessageBox. Это позволит проследить прохождение нашего события, чтобы убедиться, что оно действительно всплывает.
Запустите тестовый проект и испытайте работу таймера - функционирует как и задумали
Таким образом, мы спроектировали простой цифровой таймер и упаковали его в библиотеку. Для проверки работы таймера разработали тестирующую исполнимую сборку и подключили к ней библиотечную сборку. И все работает. Пример простой, но показательный. В дальнейшем, при развитии текущего решения или разработке новых проектов можно добавлять в ту же самую библиотеку и другие элементы управления пользовательского интерфейса.
Для начала используем программные тесты с кодом HTML. Рассмотрим два способа разбора XML -кода. Для реализации этих способов воспользуемся консольным приложением.
Командой меню File/Add/New Project добавьте к решению новый проект с именем ConvertTables и назначьте его стартовым
Оболочка автоматически создала файл Program.cs с точкой входа в приложение.
Вначале рассмотрим более слабый способ " Convert HTML Table to Array/Dataset " из "http://forums.asp.net/t/947241.aspx", который требует предварительной подготовки исходной таблицы.
В Solution Explorer вызовите контекстное меню для корня проекта ConvertTables и командой Add/Class добавьте класс с именем Convert1
Заполните класс Convert1 следующим кодомusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Xml;
namespace ConvertTables
{
class Convert1 // Source: "http://forums.asp.net/t/947241.aspx"
{
public void HtmlToDataTable()
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(@"
<table border='1' cellpadding='0' cellspacing='0'>
<tr>
<td width='50%'>cell 1a</td>
<td width='50%'>cell 1b</td>
</tr>
<tr>
<td width='50%'>cell 2a</td>
<td width='50%'>cell 2b</td>
</tr>
</table>
");
DataTable dt = new DataTable();
dt.Columns.Add("Col1");
dt.Columns.Add("Col2");
foreach (XmlNode ndRow in doc.DocumentElement.ChildNodes)
{
DataRow dr = dt.NewRow();
for (int colIndex = 0; colIndex < ndRow.ChildNodes.Count; colIndex++)
dr[colIndex] = ndRow.ChildNodes[colIndex].InnerText;
dt.Rows.Add(dr);
}
// Выводим на консоль
foreach (DataRow row in dt.Rows)
Console.WriteLine("{0} {1}", row["Col1"], row["Col2"]);
}
}
}
Добавьте в файл Program.cs с функцией Main() код выполнения класса Convert1using System;
using System.Collections.Generic;
using System.Text;
namespace ConvertTables
{
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.White;
Convert1 convert1 = new Convert1();
convert1.HtmlToDataTable();
Console.ReadLine(); // Для задержки окна
}
}
}
Запустите проект - должен получиться следующий результатcell 1a cell 1b cell 2a cell 2b
Применим код " Convert HTML tables to a DataSet " из "http://www.dotnetfunda.com/articles/article51.aspx". Этот подход более универсальный и может применяться как самостоятельно, так и в комбинации с первым способом. Вначале приведем код вместе с тестом, а затем приспособим его к своей задаче.
В Solution Explorer вызовите контекстное меню для корня проекта ConvertTables и командой Add/Class добавьте класс с именем Convert2
Наполните класс Convert2 следующим кодомusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Xml;
using System.Text.RegularExpressions;
namespace ConvertTables
{
class Convert2 // Source: "http://www.dotnetfunda.com/articles/article51.aspx"
{
public String GetHTML()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("<!DOCTYPE HTML PUBLIC " +
"-//W3C//DTD HTML 4.01 Transitional//EN" +
"http://www.w3.org/TR/html4/loose.dtd" +
">");
sb.AppendLine("<html>");
sb.AppendLine("<head>");
sb.AppendLine("<meta http-equiv=" +
"Content-Type" +
" content=" +
"text/html; charset=iso-8859-1" +
"> ");
// Первая таблица (с заголовком)
sb.AppendLine("<title>Title</title>");
sb.AppendLine("</head>");
sb.AppendLine("<body>");
sb.AppendLine("<table>");
sb.AppendLine("<tr>");
sb.AppendLine("<th>Table 1 - Header 1</th>");
sb.AppendLine("<th>Table 1 - Header 2</th>");
sb.AppendLine("<th>Table 1 - Header 3</th>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 1 - Row 1 - Column 1</td>");
sb.AppendLine("<td>Table 1 - Row 1 - Column 2</td>");
sb.AppendLine("<td>Table 1 - Row 1 - Column 3</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 1 - Row 2 - Column 1</td>");
sb.AppendLine("<td>Table 1 - Row 2 - Column 2</td>");
sb.AppendLine("<td>Table 1 - Row 2 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 1 - Row 3 - Column 1</td>");
sb.AppendLine("<td>Table 1 - Row 3 - Column 2</td>");
sb.AppendLine("<td>Table 1 - Row 3 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("</table>");
// Вторая таблица (без заголовка)
sb.AppendLine("<table>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 2 - Row 1 - Column 1</td>");
sb.AppendLine("<td>Table 2 - Row 1 - Column 2</td>");
sb.AppendLine("<td>Table 2 - Row 1 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 2 - Row 2 - Column 1</td>");
sb.AppendLine("<td>Table 2 - Row 2 - Column 2</td>");
sb.AppendLine("<td>Table 2 - Row 2 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 2 - Row 3 - Column 1</td>");
sb.AppendLine("<td>Table 2 - Row 3 - Column 2</td>");
sb.AppendLine("<td>Table 2 - Row 3 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("</table>");
sb.AppendLine("</body>");
sb.AppendLine("</html>");
return sb.ToString();
}
public DataSet ConvertHTMLTablesToDataSet(String HTML)
{
// Локальные сылки
DataSet ds = new DataSet();
DataTable dt;
DataRow dr;
// DataColumn dc;
bool HeadersExist = false; // Флаг заголовка таблицы
int iCurrentColumn = 0; // Счетчик столбцов
int iCurrentRow = 0; // Счетчик строк
// Регулярные выражения
String TableExpression = "<table[^>]*>(.*?)</table>";
String HeaderExpression = "<th[^>]*>(.*?)</th>";
String RowExpression = "<tr[^>]*>(.*?)</tr>";
String ColumnExpression = "<td[^>]*>(.*?)</td>";
RegexOptions options = RegexOptions.Multiline |
RegexOptions.Singleline |
RegexOptions.IgnoreCase;
// Заполняем коллекцию соответствий для всех таблиц HTML
MatchCollection Tables = Regex.Matches(HTML, TableExpression, options);
// Проходим по всем таблицам
foreach (Match Table in Tables)
{
// Сбрасываем счетчик строки и флаг заголовка
HeadersExist = false;
iCurrentRow = 0;
// Создаем объект новой таблицы и наполняем его
dt = new DataTable();
/* Создаем соответствующее количество столбцов
* для текущей таблицы (используем заголовки,
* если они существуют, иначе используем
* заданные по умолчанию названия)
*/
if (Table.Value.Contains("<th"))
{
HeadersExist = true;// Таблица содержит заголовок
// Заполняем коллекцию соответствий для всех <th> в таблице
MatchCollection Headers = Regex.Matches(
Table.Value, HeaderExpression, options);
// Добавляем столбцы с именами из строки заголовков текущей таблицы
foreach (Match Header in Headers)
{
dt.Columns.Add(Header.Groups[1].ToString());
}
}
else
{
Match tab = Regex.Matches(Table.Value, TableExpression, options)[0];
Match row = Regex.Matches(tab.ToString(), RowExpression, options)[0];
int count = Regex.Matches(row.ToString(), ColumnExpression, options).Count;
// Строки заголовков нет, добавляем дежурные имена
for (int iColumns = 0; iColumns < count; iColumns++)
dt.Columns.Add("Column" + iColumns);
}
// Извлекаем коллекцию строк текущей таблицы
MatchCollection Rows = Regex.Matches(Table.Value, RowExpression, options);
// Добавляем строки в объект DataRow
foreach (Match Row in Rows)
{
// Игнорируем строку заголовка
if (iCurrentRow != 0 || HeadersExist != true)
{
dr = dt.NewRow();
iCurrentColumn = 0;
// Извлекаем коллекцию столбцов текущей строки
MatchCollection Columns = Regex.Matches(Row.Value, ColumnExpression, options);
foreach (Match Column in Columns)
{
dr[iCurrentColumn] = Column.Groups[1].ToString();
iCurrentColumn++;
}
dt.Rows.Add(dr);
}
iCurrentRow++;
}
ds.Tables.Add(dt);
}
return ds;
}
}
}
Удалите в файле Program.cs код выполнения класса Convert1 и добавьте код выполнения класса Convert2. В результате должно получиться следующееusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace ConvertTables
{
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.White;
Convert2 convert2 = new Convert2();
String source = convert2.GetHTML();
DataSet dataSet = convert2.ConvertHTMLTablesToDataSet(source);
for (int i = 0; i < dataSet.Tables.Count; i++)
{
// Распечатываем заголовки столбцов
foreach (DataColumn col in dataSet.Tables[i].Columns)
Console.Write("{0} ", col.ColumnName);
Console.WriteLine();
// Распечатываем строки
foreach (DataRow row in dataSet.Tables[i].Rows)
{
foreach (DataColumn col in dataSet.Tables[i].Columns)
Console.Write("{0} ", row[col].ToString());
Console.WriteLine();
}
Console.WriteLine();
}
Console.ReadLine(); // Для задержки окна
}
}
}
Запустите проект - должен получиться следующий результатTable 1 - Header 1 Table 1 - Header 2 Table 1 - Header 3 Table 1 - Row 1 - Column 1 Table 1 - Row 1 - Column 2 Table 1 - Row 1 - Column 3 Table 1 - Row 2 - Column 1 Table 1 - Row 2 - Column 2 Table 1 - Row 2 - Column 3 Table 1 - Row 3 - Column 1 Table 1 - Row 3 - Column 2 Table 1 - Row 3 - Column 3 Column0 Column1 Column2 Table 2 - Row 1 - Column 1 Table 2 - Row 1 - Column 2 Table 2 - Row 1 - Column 3 Table 2 - Row 2 - Column 1 Table 2 - Row 2 - Column 2 Table 2 - Row 2 - Column 3 Table 2 - Row 3 - Column 1 Table 2 - Row 3 - Column 2 Table 2 - Row 3 - Column 3
Воспользуемся вторым способом извлечения информации из HTML -таблиц в DataSet и немного изменим задачу. Пусть у нас имеются HTML -файлы исключительно с таблицами текстов одинаковой структуры, например, пусть одна из таблиц будет такой
| QueryID | Query | CorrectAnswer | Comment | Theme |
|---|---|---|---|---|
| 1_4 | Каким транспортным средствам в обозначенной знаком зоне запрещена стоянка в выходные и праздничные дни ? 1. Только грузовым автомобилям с разрешенной максимальной массой более 3,5 т. 2. Всем грузовым автомобилям. 3. Всем транспортным средствам. | 1 | Знак 5.27 "Зона с ограничением стоянки" относится к зональным знакам, требования которых действуют на всей территории (участке дороги), обозначенной такими знаками, вплоть до выезда из зоны, обозначенного в данном случае знаком 5.28 "Конец зоны с ограничением стоянки". Табличка 8.5.1, размещенная под знаком, распространяет его действие только на субботние, воскресные и праздничные дни. А изображение таблички 8.4.1 "Вид транспортного средства" в нижней части знака информирует о том, что в обозначенной зоне стоянка запрещена только грузовым автомобилям, у которых разрешенная максимальная масса превышает 3,5 т. | 4 |
| 1_9 | По какой траектории Вы имеете право выполнить разворот, управляя автопоездом, имеющим большую длину ? 1. Только по А. 2. Только по Б. 3. По любой. | 1 | При недостаточной для разворота ширине проезжей части Правила разрешают вне перекрестка выполнение маневра не из крайнего левого положения, а с обочины или от правого края проезжей части (п. 8.8), т.е. по траектории А. | 8 |
| 1_10 | С какой скоростью Вы можете продолжить движение вне населенного пункта по левой полосе на грузовом автомобиле с разрешенной максимальной массой более 3,5 т ? 1. Не более 50 км/ч. 2. Не менее 50 км/ч и не более 90 км/ч. 3. Не менее 50 км/ч и не более 70 км/ч. | 3 | Знак 4.6 "Ограничение минимальной скорости" и табличка 8.14 "Полоса движения" предписывают двигаться по левой полосе со скоростью не менее 50 км/ч. Однако при этом на дороге вне населенного пункта, не относящейся к автомагистрали, вы не имеете права развивать скорость на грузовом автомобиле более 70 км/ч (п. 10.3). | 10 |
Для правильной работы утилиты таблица должна удовлетворять следующим условиям:
Наша задача, разработать утилиту, которая бы заполнила ADO.NET -объект DataTable или DataSet содержимым одной таблицы <table> или группы таблиц. Утилита будет способна обрабатывать много таблиц, но применим ее только к одной таблице Ticket1.htm, которая приведена в каталоге Source. При желании, можно развить эту утилиту до функциональности, чтобы она создавала и заполняла данными, извлеченными из <table>, таблицу (или несколько таблиц) базы данных.
В процессе преобразования исходной таблицы HTML будем заполнять наборы данных для вопросов и отдельно для ответов, чтобы получить следующие представления (на примере одной исходной таблицы)
| QueryID | Query | CorrectAnswer | Comment | Theme |
|---|---|---|---|---|
| 4 | Каким транспортным средствам в обозначенной знаком зоне запрещена стоянка в выходные и праздничные дни ? | 1 | Знак 5.27 "Зона с ограничением стоянки" относится к зональным знакам, требования которых действуют на всей территории (участке дороги), обозначенной такими знаками, вплоть до выезда из зоны, обозначенного в данном случае знаком 5.28 "Конец зоны с ограничением стоянки". Табличка 8.5.1, размещенная под знаком, распространяет его действие только на субботние, воскресные и праздничные дни. А изображение таблички 8.4.1 "Вид транспортного средства" в нижней части знака информирует о том, что в обозначенной зоне стоянка запрещена только грузовым автомобилям, у которых разрешенная максимальная масса превышает 3,5 т. | 4 |
| 9 | По какой траектории Вы имеете право выполнить разворот, управляя автопоездом, имеющим большую длину ? | 1 | При недостаточной для разворота ширине проезжей части Правила разрешают вне перекрестка выполнение маневра не из крайнего левого положения, а с обочины или от правого края проезжей части (п. 8.8), т.е. по траектории А. | 8 |
| 10 | С какой скоростью Вы можете продолжить движение вне населенного пункта по левой полосе на грузовом автомобиле с разрешенной максимальной массой более 3,5 т ? | 3 | Знак 4.6 "Ограничение минимальной скорости" и табличка 8.14 "Полоса движения" предписывают двигаться по левой полосе со скоростью не менее 50 км/ч. Однако при этом на дороге вне населенного пункта, не относящейся к автомагистрали, вы не имеете права развивать скорость на грузовом автомобиле более 70 км/ч (п. 10.3). | 10 |
| QueryID | AnswerID | Answer |
|---|---|---|
| 4 | 1 | Только грузовым автомобилям с разрешенной максимальной массой более 3,5 т. |
| 4 | 2 | Всем грузовым автомобилям. |
| 4 | 3 | Всем транспортным средствам. |
| 9 | 1 | Только по А. |
| 9 | 2 | Только по Б. |
| 9 | 3 | По любой. |
| 10 | 1 | Не более 50 км/ч. |
| 10 | 2 | Не менее 50 км/ч и не более 90 км/ч. |
| 10 | 3 | Не менее 50 км/ч и не более 70 км/ч. |
Добавьте к решению новый проект консольного приложения с именем CreateDataSet и назначьте его стартовым в решении
Добавьте к проекту новый класс с именем CreateData в файле CreateData.cs и заполните его такusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Text.RegularExpressions;
namespace CreateDataSet
{
public enum SelectTicket
{
AB,
CD,
ABCD
}
class CreateData
{
// Source: "http://www.dotnetfunda.com/articles/article51.aspx"
void ConvertHTMLTablesToDataSet(
String HTML, DataSet dsQuestions, DataSet dsAnswers)
{
// Локальные сылки
DataTable dtQuestions;
DataRow drQuestions;
DataTable dtAnswers;
DataRow drAnswers;
bool HeadersExist = false; // Флаг существования заголовка таблицы
int iCurrentColumn = 0; // Счетчик столбцов
int iCurrentRow = 0; // Счетчик строк
// Регулярные выражения и опции
String TableExpression = "<table[^>]*>(.*?)</table>";
String HeaderExpression = "<th[^>]*>(.*?)</th>";
String RowExpression = "<tr[^>]*>(.*?)</tr>";
String ColumnExpression = "<td[^>]*>(.*?)</td>";
String ParagraphExpression = "<p[^>]*>(.*?)</p>";
RegexOptions options = RegexOptions.Multiline |
RegexOptions.Singleline |
RegexOptions.IgnoreCase;
// Извлекаем коллекцию таблиц из содержимого HTML
MatchCollection Tables = Regex.Matches(HTML, TableExpression, options);
// Проходим по всем HTML-таблицам
foreach (Match Table in Tables)
{
// Сбрасываем счетчик строки и флаг заголовка
HeadersExist = false;
iCurrentRow = 0;
// Создаем объект новой таблицы и наполняем его
dtQuestions = new DataTable();
dtAnswers = new DataTable();
/* Создаем соответствующее количество столбцов
* для текущей таблицы (используем заголовки,
* если они существуют, иначе используем
* заданные по умолчанию названия)
*/
if (Table.Value.Contains("<th"))
{
HeadersExist = true;// Таблица содержит заголовок
// Заполняем коллекцию соответствий для всех <th> в таблице
MatchCollection Headers = Regex.Matches(
Table.Value, HeaderExpression, options);
// Добавляем столбцы с именами из строки заголовков текущей таблицы
foreach (Match Header in Headers)
{
// Groups[0] - с дескрипторами <p>Содержимое</p>
// Groups[1] - только Содержимое
MatchCollection paragraphs = Regex.Matches(Header.Value, ParagraphExpression, options);
if (paragraphs.Count != 0)
dtQuestions.Columns.Add(paragraphs[0].Groups[1].ToString());
else
dtQuestions.Columns.Add(Header.Groups[1].ToString());
}
}
else
{
Match tab = Regex.Matches(Table.Value, TableExpression, options)[0];
Match row = Regex.Matches(tab.ToString(), RowExpression, options)[0];
int count = Regex.Matches(row.ToString(), ColumnExpression, options).Count;
// Строки заголовков нет, добавляем дежурные имена
for (int iColumns = 0; iColumns < count; iColumns++)
dtQuestions.Columns.Add("Column" + iColumns);
}
String[] TableAnswerColumnsName = { "QueryID", "AnswerID", "Answer" };
for (int iColumns = 0; iColumns < TableAnswerColumnsName.Length; iColumns++)
dtAnswers.Columns.Add(TableAnswerColumnsName[iColumns]);
// Извлекаем коллекцию строк текущей таблицы
MatchCollection Rows = Regex.Matches(Table.Value, RowExpression, options);
// Добавляем строки в объект DataRow
foreach (Match Row in Rows)
{
// Игнорируем строку заголовка
if (iCurrentRow != 0 || HeadersExist != true)
{
drQuestions = dtQuestions.NewRow();
iCurrentColumn = 0;
// Извлекаем коллекцию столбцов текущей строки
MatchCollection Columns = Regex.Matches(Row.Value, ColumnExpression, options);
foreach (Match Column in Columns)
{
// Извлекаем коллекцию параграфов из текущего столбца
MatchCollection paragraphs = Regex.Matches(Column.Value, ParagraphExpression, options);
if (paragraphs.Count != 0)
{
int iAnswer = 0;
String line = String.Empty;
foreach (Match paragraph in paragraphs)
{
line = paragraph.Groups[1].ToString();
// Убираем все лишнее
string[] split = line.Split(new Char[] { '\r', '\n', ' ' });
StringBuilder strBuilder = new StringBuilder();
foreach (string s in split)
{
string word = s.Trim();
if (word != String.Empty)
strBuilder.Append(" " + word);
}
line = strBuilder.ToString().Trim();
// Убираем номера ответов
int pos = line.IndexOf(".");
if (pos > 0 pos <= 2 iCurrentColumn == 1)
{
// Ответы
drAnswers = dtAnswers.NewRow();
drAnswers[0] = drQuestions[0];
iAnswer++;
drAnswers[1] = iAnswer.ToString();
line = line.Substring(pos + 1).Trim();
drAnswers[2] = line;
dtAnswers.Rows.Add(drAnswers);
continue;
}
if (iAnswer == 0)
drQuestions[iCurrentColumn] = line;
}
}
else
drQuestions[iCurrentColumn] = Column.Groups[1].ToString();
iCurrentColumn++;
}
dtQuestions.Rows.Add(drQuestions);
}
iCurrentRow++;
}
dsQuestions.Tables.Add(dtQuestions);
dsAnswers.Tables.Add(dtAnswers);
}
}
void FillDataset(string path, DataSet dsQuestions, DataSet dsAnswers, int count)
{
for (int i = 1; i <= count; i++)
{
string file = path + "\\Ticket" + i.ToString() + ".htm";
System.IO.StreamReader streamReader = new System.IO.StreamReader(
file, Encoding.GetEncoding(1251));
String source = streamReader.ReadToEnd();
DataSet dsQuestionsItem = new DataSet();
DataSet dsAnswersItem = new DataSet();
ConvertHTMLTablesToDataSet(source, dsQuestionsItem, dsAnswersItem);
foreach (DataTable table in dsQuestionsItem.Tables)
{
DataRow[] rows = new DataRow[table.Rows.Count];
for (int iRow = 0; iRow < rows.Length; iRow++)
{
rows[iRow] = table.Rows[iRow];
rows[iRow][0] = (rows[iRow][0]).ToString().Trim();
}
dsQuestions.Merge(rows);
}
foreach (DataTable table in dsAnswersItem.Tables)
{
DataRow[] rows = new DataRow[table.Rows.Count];
for (int iRow = 0; iRow < rows.Length; iRow++)
{
rows[iRow] = table.Rows[iRow];
rows[iRow][0] = (rows[iRow][0]).ToString().Trim();
}
dsAnswers.Merge(rows);
}
}
}
// Поля
DataSet dsQuestionsAB = new DataSet();
DataSet dsAnswersAB = new DataSet();
DataSet dsQuestionsCD = new DataSet();
DataSet dsAnswersCD = new DataSet();
int lenQuery = 0, lenComment = 0, lenAnswer = 0;
int countTicket = 40; // Количество файлов-билетов
// Свойства
public DataSet QuestionsAB { get { return dsQuestionsAB; } }
public DataSet AnswersAB { get { return dsAnswersAB; } }
public DataSet QuestionsCD { get { return dsQuestionsCD; } }
public DataSet AnswersCD { get { return dsAnswersCD; } }
public int LenQuery { get { return lenQuery; } }
public int LenComment { get { return lenComment; } }
public int LenAnswer { get { return lenAnswer; } }
public int CountTicket { set { countTicket = value; } }
public void Execute(SelectTicket selectTicket)
{
string path = System.IO.Directory.GetCurrentDirectory();
int len;
switch (selectTicket)
{
case SelectTicket.AB:
// Заполняем DataSet
FillDataset(path + @"\AB", dsQuestionsAB, dsAnswersAB, countTicket);
// Считаем размеры полей для создания объектных таблиц
foreach (DataRow row in dsQuestionsAB.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersAB.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
break;
case SelectTicket.CD:
// Заполняем DataSet
FillDataset(path + @"\CD", dsQuestionsCD, dsAnswersCD, countTicket);
// Считаем размеры полей для создания объектных таблиц
foreach (DataRow row in dsQuestionsCD.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersCD.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
break;
case SelectTicket.ABCD:
// Заполняем DataSet
FillDataset(path + @"\AB", dsQuestionsAB, dsAnswersAB, countTicket);
FillDataset(path + @"\CD", dsQuestionsCD, dsAnswersCD, countTicket);
// Считаем размеры полей для создания объектных таблиц
foreach (DataRow row in dsQuestionsAB.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersAB.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
foreach (DataRow row in dsQuestionsCD.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersCD.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
break;
}
}
public void Show(DataSet dsQuestions, DataSet dsAnswers)
{
for (int i = 0; i < dsQuestions.Tables.Count; i++)
{
// Распечатываем заголовки столбцов
foreach (DataColumn col in dsQuestions.Tables[i].Columns)
Console.WriteLine("{0}", col.ColumnName);
Console.WriteLine();
// Распечатываем строки
foreach (DataRow row in dsQuestions.Tables[i].Rows)
{
foreach (DataColumn col in dsQuestions.Tables[i].Columns)
Console.WriteLine("{0}", row[col].ToString());
Console.WriteLine();
}
Console.WriteLine();
}
for (int i = 0; i < dsAnswers.Tables.Count; i++)
{
// Распечатываем заголовки столбцов
foreach (DataColumn col in dsAnswers.Tables[i].Columns)
Console.WriteLine("{0}", col.ColumnName);
Console.WriteLine();
// Распечатываем строки
foreach (DataRow row in dsAnswers.Tables[i].Rows)
{
foreach (DataColumn col in dsAnswers.Tables[i].Columns)
Console.WriteLine("{0}", row[col].ToString());
Console.WriteLine();
}
Console.WriteLine();
}
}
}
}
При создании экземпляра этого класса в клиенте должен быть произведен разбор кода HTML -таблиц и заполнены 4 (если режим SelectTicket.ABCD ) набора данных типа DataSet, ссылки на которые доступны через свойства QuestionsAB, AnswersAB, QuestionsCD, AnswersCD. Попутно будут вычислены максимальные размеры полей LenQuery, LenComment, LenAnswer.
Модифицируйте файл Program.cs следующим образомusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace CreateDataSet
{
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.White;
Console.WindowWidth = 81;
CreateData tables = new CreateData();
tables.CountTicket = 1;
tables.Execute(SelectTicket.CD);
tables.Show(tables.QuestionsCD, tables.AnswersCD);
Console.WriteLine(String.Format("LenQuery={0}; LenComment={1}; LenAnswer={2}",
tables.LenQuery, tables.LenComment, tables.LenAnswer));
Console.ReadLine();
}
}
}
Через панель Solution Explorer создайте в корне текущего проекта папку CD и скопируйте в нее контекстной командой Add/Existing Item из прилагаемого к работе каталога Source файл Ticket1.htm
В Solution Explorer выделите файл Ticket1.htm и в панели Properties назначьте ему свойство
Запустите проект на выполнение - получится следующий консольный выводQueryID Query CorrectAnswer Comment Theme 1_4 Каким транспортным средствам в обозначенной знаком зоне запрещена стоянка в выходные и праздничные дни ? 1 Знак 5.27 "Зона с ограничением стоянки" относится к зональным знакам, требования которых действуют на всей территории (участке дороги), обозначенной такими знаками, вплоть до выезда из зоны, обозначенного в данном случае знаком 5.28 "Конец зоны с ограничением стоянки". Табличка 8.5.1, размещенная под знаком, распространяет его действие только на субботние, воскресные и праздничные дни. А изображение таблички 8.4.1 "Вид транспортного средства" в нижней части знака информирует о том, что в обозначенной зоне стоянка запрещена только грузовым автомобилям, у которых разрешенная максимальная масса превышает 3,5 т. 4 1_9 По какой траектории Вы имеете право выполнить разворот, управляя автопоездом, имеющим большую длину ? 1 При недостаточной для разворота ширине проезжей части Правила разрешают вне перекрестка выполнение маневра не из крайнего левого положения, а с обочины или от правого края проезжей части (п. 8.8), т.е. по траектории А. 8 1_10 С какой скоростью Вы можете продолжить движение вне населенного пункта по левой полосе на грузовом автомобиле с С разрешенной максимальной массой более 3,5 т ? 3 Знак 4.6 "Ограничение минимальной скорости" и табличка 8.14 "Полоса движения" предписывают двигаться по левой полосе со скоростью не менее 50 км/ч. Однако при этом на дороге вне населенного пункта, не относящейся к автомагистрали, вы не имеете права развивать скорость на грузовом автомобиле более 70 км/ч (п. 10.3). 10 QueryID AnswerID Answer 1_4 1 Только грузовым автомобилям с разрешенной максимальной массой более 3,5 т. 1_4 2 Всем грузовым автомобилям. 1_4 3 Всем транспортным средствам. 1_9 1 Только по А. 1_9 2 Только по Б. 1_9 3 По любой. 1_10 1 Не более 50 км/ч. 1_10 2 Не менее 50 км/ч и не более 90 км/ч. 1_10 3 Не менее 50 км/ч и не более 70 км/ч. LenQuery=157; LenComment=615; LenAnswer=74
Мы видим, что данные извлекаются утилитой из HTML -таблицы правильно и размещаются построчно в соответствующих полях объекта DataSet.
В заключение приведем интересный способ регулирования ширины окна WPF, приведенный в http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/a4b5150f-5d70-4d33-9355-4cac9521677d
Создайте приложение WPF Application с именем WpfApplication1, внесите в него приведенный ниже код и испытайте работу<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Name="window1" MouseWheel="Window_MouseWheel">
<Grid>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
namespace WpfApplication1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
{
int delta = e.Delta;
double w1 = this.window1.ActualWidth;
double w2 = w1 + 2 * delta / 10;
DoubleAnimation anima = new DoubleAnimation();
anima.Duration = new Duration(TimeSpan.FromSeconds(0.3));
anima.From = w1;
anima.To = w2;
anima.FillBehavior = FillBehavior.HoldEnd;
this.window1.BeginAnimation(Window.WidthProperty, anima);
}
}
}
Внимательно разберитесь со всем приведенным кодом упражнений. Такой подход - самый короткий путь научиться программировать
Все необходимые для выполнения данной работы программы можно найти в Setup.zip.
В WPF предусмотрены два вида пользовательских элементов:
Настраиваемые пользовательские элементы невидимы. Для них проектная плоскость Design с возможностью перетаскивания элементов из панели Toolbox не предусмотрена, поэтому надо создавать шаблон. А пользовательские элементы управления имеют проектную плоскость времени проектирования и их можно создавать точно также, как проект интерфейса приложения WPF Application. Но запускать на выполнение пользовательские элементы управления можно только с помощью исполнимого теста.
Поставим задачу разработать секундомер, показывающий время, оставшееся до наступления какого-нибудь события. Объект таймера оформим как пользовательский элемент управления, помещенный в библиотеку типа WPF User Control Library. При построении используем механизмы ресурсов и привязки WPF.
Создайте решение ClockAndConvertHTML и новый проект библиотеки WpfControlLibrary, используя шаблон WPF User Control Library в типе проектов Visual Studio
В Solution Explorer переименуйте автоматически сгенерированный файл UserControl1.xaml в Clock.xaml
В редакторе файла Clock.xaml на вкладке XAML жестом Ctrl+H вызовите окно замены с настройкой Current Project и замените кнопкой Replace All все вхождения UserControl1 на Clock
В панели Solution Explorer вызовите контекстное меню для корневого узла WpfControlLibrary проекта и добавьте папку Code командой Add/New Folder
В панели Solution Explorer вызовите контекстное меню для папки Code и командой Add/Class добавьте в нее файл с именем DigitalClock.cs, который заполните такusing System;
using System.Windows; // DependencyObject, DependencyProperty
using System.ComponentModel; // INotifyPropertyChanged
using System.Windows.Threading; // DispatcherTimer
using System.Globalization; // CultureInfo
using System.Windows.Controls; // TextBlock
using System.Windows.Media; // FontFamily
namespace MyClock
{
public class DigitalClock : TextBlock, INotifyPropertyChanged
{
const int COUNT_TIME = 10; // Минуты
// Событие реализации интерфейса INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
// Обычное событие остановки таймера
public event EventHandler StopDigitalClock;
// Открытое свойство
int minutes, seconds, time;
public TextBlock Time
{
get
{
minutes = time / 60;
seconds = time % 60;
this.Text = String.Format("{0:00}:{1:D2}", minutes, seconds);
if (time > 0)
time -= 1;
return this;
}
}
// Конструктор запускает таймер
DispatcherTimer timer;
public DigitalClock()
{
// Настраиваем дизайн
this.FontFamily = new FontFamily("Arial");
this.FontSize = 1.3333 * 36; // 36pt
this.FontWeight = FontWeights.Bold;
this.Background = Brushes.Red;
this.Foreground = Brushes.Yellow;
this.Padding = new Thickness(5, 0, 5, 0);
// Создаем таймер
timer = new DispatcherTimer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = TimeSpan.FromSeconds(1);
// Начальное значение
time = COUNT_TIME * 60; // Секунды
}
public void Start()
{
timer.Stop();
timer.Start();
}
public void Restart()
{
time = COUNT_TIME * 60; // Секунды
timer.Stop();
timer.Start();
}
public void StopResult()
{
timer.Stop();
if (StopDigitalClock != null)
StopDigitalClock(this, EventArgs.Empty);
}
public void Stop()
{
timer.Stop();
}
// Обработчик события таймера возбуждает свойство Time
// и генерирует обычное событие останова
void timer_Tick(object sender, EventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Time"));
if (minutes <= 0 seconds <= 0)
{
timer.Stop();
if (StopDigitalClock != null)
StopDigitalClock(this, EventArgs.Empty);
}
}
}
}
Класс возбуждает обычное событие StopDigitalClock, когда заканчивается лимит времени.
Откройте на редактирование файл Clock.xaml в представлении XAML и заполните его следующей разметкой<UserControl x:Class="WpfControlLibrary.Clock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyClock"
>
<UserControl.Resources>
<local:DigitalClock x:Key="clock" StopDigitalClock="DigitalClock_StopDigitalClock" />
</UserControl.Resources>
<Label Content="{Binding Source={StaticResource clock}, Path=Time}"
BorderBrush="Blue" BorderThickness="5" Padding="0"
HorizontalAlignment="Center" />
</UserControl>
Откройте на редактирование файл Clock.xaml.cs и заполните его следующим процедурным кодом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.Navigation;
using System.Windows.Shapes;
using MyClock; // Пространство имен таймера в библиотеке WpfControlLibrary
namespace WpfControlLibrary
{
public partial class Clock : UserControl
{
public Clock()
{
InitializeComponent();
// Извлекаем объект из коллекции ресурсов
clock = this.Resources["clock"] as DigitalClock;
}
DigitalClock clock; // Ссылка на экземпляр из ресурсов
// Поле маршрутизированного события
public static readonly RoutedEvent StopClockEvent;
// Статический конструктор
static Clock()
{
// Регистрация маршрутизированного события
StopClockEvent = EventManager.RegisterRoutedEvent(
"StopClock", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(Clock));
}
// Контейнер для подписки на маршрутизированное событие
public event RoutedEventHandler StopClock
{
add { AddHandler(StopClockEvent, value); }
remove { RemoveHandler(StopClockEvent, value); }
}
// Возбуждение маршрутизированного события через обработчик простого события
private void DigitalClock_StopDigitalClock(object sender, EventArgs e)
{
this.RaiseEvent(new RoutedEventArgs(Clock.StopClockEvent));
}
// Функции доступа для управления таймером из клиента
public void Start()
{
clock.Start();
}
public void Restart()
{
clock.Restart();
}
public void StopResult()
{
clock.StopResult();
}
public void Stop()
{
clock.Stop();
}
}
}
Обычное событие StopDigitalClock через обработчик возбуждает всплывающее событие StopClock, которое в клиенте можно будет слушать и управлять интерфейсом. Для управления таймером в классе Clock предусмотрены функции доступа.
Выполните команду меню Build/Build Solution или жест F6, чтобы создать библиотечную сборку
В панели Solution Explorer щелкните на пиктограмме Show All Files и убедитесь, что сборка WpfControlLibrary.dll действительно создана
Проверим работу
Командой меню оболочки File/Add/New Project добавьте к решению исполнимую сборку типа WPF Application с именем TestClock
В панели Solution Explorer вызовите контекстное меню для узла TestClock и командой Set as StartUp Project назначьте его стартовым. Обратите внимание, что после выполнение этой команды узел
В Solution Explorer выполните для узла References проекта TestClock команду Add Reference контекстного меню и на вкладке Projects подключите библиотечную сборку WpfControlLibrary.dll к тестовому проекту
В Solution Explorer выделите любой узел тестового проекта TestClock, щелкните на пиктограмме Show All Files и раскройте узел bin\Debug - убедитесь, что пока он пустой
Откомпилируйте тестовый проект и щелкните в Solution Explorer на пиктограмме Refresh - убедитесь, что оболочка скопировала в каталог размещения тестовой сборки нашу библиотечную сборку WpfControlLibrary.dll
Откройте на редактирование файл Window1.xaml в представлении XAML и заполните его следующей разметкой<Window x:Class="TestClock.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lib="clr-namespace:WpfControlLibrary;assembly=WpfControlLibrary"
Title="TestClock" Height="300" Width="300"
lib:Clock.StopClock="Window_StopClock"
>
<StackPanel lib:Clock.StopClock="StackPanel_StopClock">
<lib:Clock Name="timer" />
<Button Click="start_Click">Start</Button>
<Button Click="restart_Click">Restart</Button>
<Button Click="stopResult_Click">StopResult</Button>
<Button Click="stop_Click">Stop</Button>
</StackPanel>
</Window>
Заполните файл Window1.xaml.cs следующим процедурным кодом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.Navigation;
using System.Windows.Shapes;
namespace TestClock
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void start_Click(object sender, RoutedEventArgs e)
{
timer.Start();
}
private void restart_Click(object sender, RoutedEventArgs e)
{
timer.Restart();
}
private void stopResult_Click(object sender, RoutedEventArgs e)
{
timer.StopResult();
}
private void stop_Click(object sender, RoutedEventArgs e)
{
timer.Stop();
}
private void Window_StopClock(object sender, RoutedEventArgs e)
{
MessageBox.Show("StopClock", "Window1");
}
private void StackPanel_StopClock(object sender, RoutedEventArgs e)
{
MessageBox.Show("StopClock", "StackPanel");
}
}
}
На прослушивание всплывающего события StopClock мы в разметке подписали два интерфейсных элемента: Window и StackPanel, которые в своих обработчиках выдают диалоговое окно MessageBox. Это позволит проследить прохождение нашего события, чтобы убедиться, что оно действительно всплывает.
Запустите тестовый проект и испытайте работу таймера - функционирует как и задумали
Таким образом, мы спроектировали простой цифровой таймер и упаковали его в библиотеку. Для проверки работы таймера разработали тестирующую исполнимую сборку и подключили к ней библиотечную сборку. И все работает. Пример простой, но показательный. В дальнейшем, при развитии текущего решения или разработке новых проектов можно добавлять в ту же самую библиотеку и другие элементы управления пользовательского интерфейса.
Для начала используем программные тесты с кодом HTML. Рассмотрим два способа разбора XML -кода. Для реализации этих способов воспользуемся консольным приложением.
Командой меню File/Add/New Project добавьте к решению новый проект с именем ConvertTables и назначьте его стартовым
Оболочка автоматически создала файл Program.cs с точкой входа в приложение.
Вначале рассмотрим более слабый способ " Convert HTML Table to Array/Dataset " из "http://forums.asp.net/t/947241.aspx", который требует предварительной подготовки исходной таблицы.
В Solution Explorer вызовите контекстное меню для корня проекта ConvertTables и командой Add/Class добавьте класс с именем Convert1
Заполните класс Convert1 следующим кодомusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Xml;
namespace ConvertTables
{
class Convert1 // Source: "http://forums.asp.net/t/947241.aspx"
{
public void HtmlToDataTable()
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(@"
<table border='1' cellpadding='0' cellspacing='0'>
<tr>
<td width='50%'>cell 1a</td>
<td width='50%'>cell 1b</td>
</tr>
<tr>
<td width='50%'>cell 2a</td>
<td width='50%'>cell 2b</td>
</tr>
</table>
");
DataTable dt = new DataTable();
dt.Columns.Add("Col1");
dt.Columns.Add("Col2");
foreach (XmlNode ndRow in doc.DocumentElement.ChildNodes)
{
DataRow dr = dt.NewRow();
for (int colIndex = 0; colIndex < ndRow.ChildNodes.Count; colIndex++)
dr[colIndex] = ndRow.ChildNodes[colIndex].InnerText;
dt.Rows.Add(dr);
}
// Выводим на консоль
foreach (DataRow row in dt.Rows)
Console.WriteLine("{0} {1}", row["Col1"], row["Col2"]);
}
}
}
Добавьте в файл Program.cs с функцией Main() код выполнения класса Convert1using System;
using System.Collections.Generic;
using System.Text;
namespace ConvertTables
{
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.White;
Convert1 convert1 = new Convert1();
convert1.HtmlToDataTable();
Console.ReadLine(); // Для задержки окна
}
}
}
Запустите проект - должен получиться следующий результатcell 1a cell 1b cell 2a cell 2b
Применим код " Convert HTML tables to a DataSet " из "http://www.dotnetfunda.com/articles/article51.aspx". Этот подход более универсальный и может применяться как самостоятельно, так и в комбинации с первым способом. Вначале приведем код вместе с тестом, а затем приспособим его к своей задаче.
В Solution Explorer вызовите контекстное меню для корня проекта ConvertTables и командой Add/Class добавьте класс с именем Convert2
Наполните класс Convert2 следующим кодомusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Xml;
using System.Text.RegularExpressions;
namespace ConvertTables
{
class Convert2 // Source: "http://www.dotnetfunda.com/articles/article51.aspx"
{
public String GetHTML()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("<!DOCTYPE HTML PUBLIC " +
"-//W3C//DTD HTML 4.01 Transitional//EN" +
"http://www.w3.org/TR/html4/loose.dtd" +
">");
sb.AppendLine("<html>");
sb.AppendLine("<head>");
sb.AppendLine("<meta http-equiv=" +
"Content-Type" +
" content=" +
"text/html; charset=iso-8859-1" +
"> ");
// Первая таблица (с заголовком)
sb.AppendLine("<title>Title</title>");
sb.AppendLine("</head>");
sb.AppendLine("<body>");
sb.AppendLine("<table>");
sb.AppendLine("<tr>");
sb.AppendLine("<th>Table 1 - Header 1</th>");
sb.AppendLine("<th>Table 1 - Header 2</th>");
sb.AppendLine("<th>Table 1 - Header 3</th>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 1 - Row 1 - Column 1</td>");
sb.AppendLine("<td>Table 1 - Row 1 - Column 2</td>");
sb.AppendLine("<td>Table 1 - Row 1 - Column 3</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 1 - Row 2 - Column 1</td>");
sb.AppendLine("<td>Table 1 - Row 2 - Column 2</td>");
sb.AppendLine("<td>Table 1 - Row 2 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 1 - Row 3 - Column 1</td>");
sb.AppendLine("<td>Table 1 - Row 3 - Column 2</td>");
sb.AppendLine("<td>Table 1 - Row 3 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("</table>");
// Вторая таблица (без заголовка)
sb.AppendLine("<table>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 2 - Row 1 - Column 1</td>");
sb.AppendLine("<td>Table 2 - Row 1 - Column 2</td>");
sb.AppendLine("<td>Table 2 - Row 1 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 2 - Row 2 - Column 1</td>");
sb.AppendLine("<td>Table 2 - Row 2 - Column 2</td>");
sb.AppendLine("<td>Table 2 - Row 2 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("<tr>");
sb.AppendLine("<td>Table 2 - Row 3 - Column 1</td>");
sb.AppendLine("<td>Table 2 - Row 3 - Column 2</td>");
sb.AppendLine("<td>Table 2 - Row 3 - Column 3</td>");
sb.AppendLine("</td>");
sb.AppendLine("</tr>");
sb.AppendLine("</table>");
sb.AppendLine("</body>");
sb.AppendLine("</html>");
return sb.ToString();
}
public DataSet ConvertHTMLTablesToDataSet(String HTML)
{
// Локальные сылки
DataSet ds = new DataSet();
DataTable dt;
DataRow dr;
// DataColumn dc;
bool HeadersExist = false; // Флаг заголовка таблицы
int iCurrentColumn = 0; // Счетчик столбцов
int iCurrentRow = 0; // Счетчик строк
// Регулярные выражения
String TableExpression = "<table[^>]*>(.*?)</table>";
String HeaderExpression = "<th[^>]*>(.*?)</th>";
String RowExpression = "<tr[^>]*>(.*?)</tr>";
String ColumnExpression = "<td[^>]*>(.*?)</td>";
RegexOptions options = RegexOptions.Multiline |
RegexOptions.Singleline |
RegexOptions.IgnoreCase;
// Заполняем коллекцию соответствий для всех таблиц HTML
MatchCollection Tables = Regex.Matches(HTML, TableExpression, options);
// Проходим по всем таблицам
foreach (Match Table in Tables)
{
// Сбрасываем счетчик строки и флаг заголовка
HeadersExist = false;
iCurrentRow = 0;
// Создаем объект новой таблицы и наполняем его
dt = new DataTable();
/* Создаем соответствующее количество столбцов
* для текущей таблицы (используем заголовки,
* если они существуют, иначе используем
* заданные по умолчанию названия)
*/
if (Table.Value.Contains("<th"))
{
HeadersExist = true;// Таблица содержит заголовок
// Заполняем коллекцию соответствий для всех <th> в таблице
MatchCollection Headers = Regex.Matches(
Table.Value, HeaderExpression, options);
// Добавляем столбцы с именами из строки заголовков текущей таблицы
foreach (Match Header in Headers)
{
dt.Columns.Add(Header.Groups[1].ToString());
}
}
else
{
Match tab = Regex.Matches(Table.Value, TableExpression, options)[0];
Match row = Regex.Matches(tab.ToString(), RowExpression, options)[0];
int count = Regex.Matches(row.ToString(), ColumnExpression, options).Count;
// Строки заголовков нет, добавляем дежурные имена
for (int iColumns = 0; iColumns < count; iColumns++)
dt.Columns.Add("Column" + iColumns);
}
// Извлекаем коллекцию строк текущей таблицы
MatchCollection Rows = Regex.Matches(Table.Value, RowExpression, options);
// Добавляем строки в объект DataRow
foreach (Match Row in Rows)
{
// Игнорируем строку заголовка
if (iCurrentRow != 0 || HeadersExist != true)
{
dr = dt.NewRow();
iCurrentColumn = 0;
// Извлекаем коллекцию столбцов текущей строки
MatchCollection Columns = Regex.Matches(Row.Value, ColumnExpression, options);
foreach (Match Column in Columns)
{
dr[iCurrentColumn] = Column.Groups[1].ToString();
iCurrentColumn++;
}
dt.Rows.Add(dr);
}
iCurrentRow++;
}
ds.Tables.Add(dt);
}
return ds;
}
}
}
Удалите в файле Program.cs код выполнения класса Convert1 и добавьте код выполнения класса Convert2. В результате должно получиться следующееusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace ConvertTables
{
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.White;
Convert2 convert2 = new Convert2();
String source = convert2.GetHTML();
DataSet dataSet = convert2.ConvertHTMLTablesToDataSet(source);
for (int i = 0; i < dataSet.Tables.Count; i++)
{
// Распечатываем заголовки столбцов
foreach (DataColumn col in dataSet.Tables[i].Columns)
Console.Write("{0} ", col.ColumnName);
Console.WriteLine();
// Распечатываем строки
foreach (DataRow row in dataSet.Tables[i].Rows)
{
foreach (DataColumn col in dataSet.Tables[i].Columns)
Console.Write("{0} ", row[col].ToString());
Console.WriteLine();
}
Console.WriteLine();
}
Console.ReadLine(); // Для задержки окна
}
}
}
Запустите проект - должен получиться следующий результатTable 1 - Header 1 Table 1 - Header 2 Table 1 - Header 3 Table 1 - Row 1 - Column 1 Table 1 - Row 1 - Column 2 Table 1 - Row 1 - Column 3 Table 1 - Row 2 - Column 1 Table 1 - Row 2 - Column 2 Table 1 - Row 2 - Column 3 Table 1 - Row 3 - Column 1 Table 1 - Row 3 - Column 2 Table 1 - Row 3 - Column 3 Column0 Column1 Column2 Table 2 - Row 1 - Column 1 Table 2 - Row 1 - Column 2 Table 2 - Row 1 - Column 3 Table 2 - Row 2 - Column 1 Table 2 - Row 2 - Column 2 Table 2 - Row 2 - Column 3 Table 2 - Row 3 - Column 1 Table 2 - Row 3 - Column 2 Table 2 - Row 3 - Column 3
Воспользуемся вторым способом извлечения информации из HTML -таблиц в DataSet и немного изменим задачу. Пусть у нас имеются HTML -файлы исключительно с таблицами текстов одинаковой структуры, например, пусть одна из таблиц будет такой
| QueryID | Query | CorrectAnswer | Comment | Theme |
|---|---|---|---|---|
| 1_4 | Каким транспортным средствам в обозначенной знаком зоне запрещена стоянка в выходные и праздничные дни ? 1. Только грузовым автомобилям с разрешенной максимальной массой более 3,5 т. 2. Всем грузовым автомобилям. 3. Всем транспортным средствам. | 1 | Знак 5.27 "Зона с ограничением стоянки" относится к зональным знакам, требования которых действуют на всей территории (участке дороги), обозначенной такими знаками, вплоть до выезда из зоны, обозначенного в данном случае знаком 5.28 "Конец зоны с ограничением стоянки". Табличка 8.5.1, размещенная под знаком, распространяет его действие только на субботние, воскресные и праздничные дни. А изображение таблички 8.4.1 "Вид транспортного средства" в нижней части знака информирует о том, что в обозначенной зоне стоянка запрещена только грузовым автомобилям, у которых разрешенная максимальная масса превышает 3,5 т. | 4 |
| 1_9 | По какой траектории Вы имеете право выполнить разворот, управляя автопоездом, имеющим большую длину ? 1. Только по А. 2. Только по Б. 3. По любой. | 1 | При недостаточной для разворота ширине проезжей части Правила разрешают вне перекрестка выполнение маневра не из крайнего левого положения, а с обочины или от правого края проезжей части (п. 8.8), т.е. по траектории А. | 8 |
| 1_10 | С какой скоростью Вы можете продолжить движение вне населенного пункта по левой полосе на грузовом автомобиле с разрешенной максимальной массой более 3,5 т ? 1. Не более 50 км/ч. 2. Не менее 50 км/ч и не более 90 км/ч. 3. Не менее 50 км/ч и не более 70 км/ч. | 3 | Знак 4.6 "Ограничение минимальной скорости" и табличка 8.14 "Полоса движения" предписывают двигаться по левой полосе со скоростью не менее 50 км/ч. Однако при этом на дороге вне населенного пункта, не относящейся к автомагистрали, вы не имеете права развивать скорость на грузовом автомобиле более 70 км/ч (п. 10.3). | 10 |
Для правильной работы утилиты таблица должна удовлетворять следующим условиям:
Наша задача, разработать утилиту, которая бы заполнила ADO.NET -объект DataTable или DataSet содержимым одной таблицы <table> или группы таблиц. Утилита будет способна обрабатывать много таблиц, но применим ее только к одной таблице Ticket1.htm, которая приведена в каталоге Source. При желании, можно развить эту утилиту до функциональности, чтобы она создавала и заполняла данными, извлеченными из <table>, таблицу (или несколько таблиц) базы данных.
В процессе преобразования исходной таблицы HTML будем заполнять наборы данных для вопросов и отдельно для ответов, чтобы получить следующие представления (на примере одной исходной таблицы)
| QueryID | Query | CorrectAnswer | Comment | Theme |
|---|---|---|---|---|
| 4 | Каким транспортным средствам в обозначенной знаком зоне запрещена стоянка в выходные и праздничные дни ? | 1 | Знак 5.27 "Зона с ограничением стоянки" относится к зональным знакам, требования которых действуют на всей территории (участке дороги), обозначенной такими знаками, вплоть до выезда из зоны, обозначенного в данном случае знаком 5.28 "Конец зоны с ограничением стоянки". Табличка 8.5.1, размещенная под знаком, распространяет его действие только на субботние, воскресные и праздничные дни. А изображение таблички 8.4.1 "Вид транспортного средства" в нижней части знака информирует о том, что в обозначенной зоне стоянка запрещена только грузовым автомобилям, у которых разрешенная максимальная масса превышает 3,5 т. | 4 |
| 9 | По какой траектории Вы имеете право выполнить разворот, управляя автопоездом, имеющим большую длину ? | 1 | При недостаточной для разворота ширине проезжей части Правила разрешают вне перекрестка выполнение маневра не из крайнего левого положения, а с обочины или от правого края проезжей части (п. 8.8), т.е. по траектории А. | 8 |
| 10 | С какой скоростью Вы можете продолжить движение вне населенного пункта по левой полосе на грузовом автомобиле с разрешенной максимальной массой более 3,5 т ? | 3 | Знак 4.6 "Ограничение минимальной скорости" и табличка 8.14 "Полоса движения" предписывают двигаться по левой полосе со скоростью не менее 50 км/ч. Однако при этом на дороге вне населенного пункта, не относящейся к автомагистрали, вы не имеете права развивать скорость на грузовом автомобиле более 70 км/ч (п. 10.3). | 10 |
| QueryID | AnswerID | Answer |
|---|---|---|
| 4 | 1 | Только грузовым автомобилям с разрешенной максимальной массой более 3,5 т. |
| 4 | 2 | Всем грузовым автомобилям. |
| 4 | 3 | Всем транспортным средствам. |
| 9 | 1 | Только по А. |
| 9 | 2 | Только по Б. |
| 9 | 3 | По любой. |
| 10 | 1 | Не более 50 км/ч. |
| 10 | 2 | Не менее 50 км/ч и не более 90 км/ч. |
| 10 | 3 | Не менее 50 км/ч и не более 70 км/ч. |
Добавьте к решению новый проект консольного приложения с именем CreateDataSet и назначьте его стартовым в решении
Добавьте к проекту новый класс с именем CreateData в файле CreateData.cs и заполните его такusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Text.RegularExpressions;
namespace CreateDataSet
{
public enum SelectTicket
{
AB,
CD,
ABCD
}
class CreateData
{
// Source: "http://www.dotnetfunda.com/articles/article51.aspx"
void ConvertHTMLTablesToDataSet(
String HTML, DataSet dsQuestions, DataSet dsAnswers)
{
// Локальные сылки
DataTable dtQuestions;
DataRow drQuestions;
DataTable dtAnswers;
DataRow drAnswers;
bool HeadersExist = false; // Флаг существования заголовка таблицы
int iCurrentColumn = 0; // Счетчик столбцов
int iCurrentRow = 0; // Счетчик строк
// Регулярные выражения и опции
String TableExpression = "<table[^>]*>(.*?)</table>";
String HeaderExpression = "<th[^>]*>(.*?)</th>";
String RowExpression = "<tr[^>]*>(.*?)</tr>";
String ColumnExpression = "<td[^>]*>(.*?)</td>";
String ParagraphExpression = "<p[^>]*>(.*?)</p>";
RegexOptions options = RegexOptions.Multiline |
RegexOptions.Singleline |
RegexOptions.IgnoreCase;
// Извлекаем коллекцию таблиц из содержимого HTML
MatchCollection Tables = Regex.Matches(HTML, TableExpression, options);
// Проходим по всем HTML-таблицам
foreach (Match Table in Tables)
{
// Сбрасываем счетчик строки и флаг заголовка
HeadersExist = false;
iCurrentRow = 0;
// Создаем объект новой таблицы и наполняем его
dtQuestions = new DataTable();
dtAnswers = new DataTable();
/* Создаем соответствующее количество столбцов
* для текущей таблицы (используем заголовки,
* если они существуют, иначе используем
* заданные по умолчанию названия)
*/
if (Table.Value.Contains("<th"))
{
HeadersExist = true;// Таблица содержит заголовок
// Заполняем коллекцию соответствий для всех <th> в таблице
MatchCollection Headers = Regex.Matches(
Table.Value, HeaderExpression, options);
// Добавляем столбцы с именами из строки заголовков текущей таблицы
foreach (Match Header in Headers)
{
// Groups[0] - с дескрипторами <p>Содержимое</p>
// Groups[1] - только Содержимое
MatchCollection paragraphs = Regex.Matches(Header.Value, ParagraphExpression, options);
if (paragraphs.Count != 0)
dtQuestions.Columns.Add(paragraphs[0].Groups[1].ToString());
else
dtQuestions.Columns.Add(Header.Groups[1].ToString());
}
}
else
{
Match tab = Regex.Matches(Table.Value, TableExpression, options)[0];
Match row = Regex.Matches(tab.ToString(), RowExpression, options)[0];
int count = Regex.Matches(row.ToString(), ColumnExpression, options).Count;
// Строки заголовков нет, добавляем дежурные имена
for (int iColumns = 0; iColumns < count; iColumns++)
dtQuestions.Columns.Add("Column" + iColumns);
}
String[] TableAnswerColumnsName = { "QueryID", "AnswerID", "Answer" };
for (int iColumns = 0; iColumns < TableAnswerColumnsName.Length; iColumns++)
dtAnswers.Columns.Add(TableAnswerColumnsName[iColumns]);
// Извлекаем коллекцию строк текущей таблицы
MatchCollection Rows = Regex.Matches(Table.Value, RowExpression, options);
// Добавляем строки в объект DataRow
foreach (Match Row in Rows)
{
// Игнорируем строку заголовка
if (iCurrentRow != 0 || HeadersExist != true)
{
drQuestions = dtQuestions.NewRow();
iCurrentColumn = 0;
// Извлекаем коллекцию столбцов текущей строки
MatchCollection Columns = Regex.Matches(Row.Value, ColumnExpression, options);
foreach (Match Column in Columns)
{
// Извлекаем коллекцию параграфов из текущего столбца
MatchCollection paragraphs = Regex.Matches(Column.Value, ParagraphExpression, options);
if (paragraphs.Count != 0)
{
int iAnswer = 0;
String line = String.Empty;
foreach (Match paragraph in paragraphs)
{
line = paragraph.Groups[1].ToString();
// Убираем все лишнее
string[] split = line.Split(new Char[] { '\r', '\n', ' ' });
StringBuilder strBuilder = new StringBuilder();
foreach (string s in split)
{
string word = s.Trim();
if (word != String.Empty)
strBuilder.Append(" " + word);
}
line = strBuilder.ToString().Trim();
// Убираем номера ответов
int pos = line.IndexOf(".");
if (pos > 0 pos <= 2 iCurrentColumn == 1)
{
// Ответы
drAnswers = dtAnswers.NewRow();
drAnswers[0] = drQuestions[0];
iAnswer++;
drAnswers[1] = iAnswer.ToString();
line = line.Substring(pos + 1).Trim();
drAnswers[2] = line;
dtAnswers.Rows.Add(drAnswers);
continue;
}
if (iAnswer == 0)
drQuestions[iCurrentColumn] = line;
}
}
else
drQuestions[iCurrentColumn] = Column.Groups[1].ToString();
iCurrentColumn++;
}
dtQuestions.Rows.Add(drQuestions);
}
iCurrentRow++;
}
dsQuestions.Tables.Add(dtQuestions);
dsAnswers.Tables.Add(dtAnswers);
}
}
void FillDataset(string path, DataSet dsQuestions, DataSet dsAnswers, int count)
{
for (int i = 1; i <= count; i++)
{
string file = path + "\\Ticket" + i.ToString() + ".htm";
System.IO.StreamReader streamReader = new System.IO.StreamReader(
file, Encoding.GetEncoding(1251));
String source = streamReader.ReadToEnd();
DataSet dsQuestionsItem = new DataSet();
DataSet dsAnswersItem = new DataSet();
ConvertHTMLTablesToDataSet(source, dsQuestionsItem, dsAnswersItem);
foreach (DataTable table in dsQuestionsItem.Tables)
{
DataRow[] rows = new DataRow[table.Rows.Count];
for (int iRow = 0; iRow < rows.Length; iRow++)
{
rows[iRow] = table.Rows[iRow];
rows[iRow][0] = (rows[iRow][0]).ToString().Trim();
}
dsQuestions.Merge(rows);
}
foreach (DataTable table in dsAnswersItem.Tables)
{
DataRow[] rows = new DataRow[table.Rows.Count];
for (int iRow = 0; iRow < rows.Length; iRow++)
{
rows[iRow] = table.Rows[iRow];
rows[iRow][0] = (rows[iRow][0]).ToString().Trim();
}
dsAnswers.Merge(rows);
}
}
}
// Поля
DataSet dsQuestionsAB = new DataSet();
DataSet dsAnswersAB = new DataSet();
DataSet dsQuestionsCD = new DataSet();
DataSet dsAnswersCD = new DataSet();
int lenQuery = 0, lenComment = 0, lenAnswer = 0;
int countTicket = 40; // Количество файлов-билетов
// Свойства
public DataSet QuestionsAB { get { return dsQuestionsAB; } }
public DataSet AnswersAB { get { return dsAnswersAB; } }
public DataSet QuestionsCD { get { return dsQuestionsCD; } }
public DataSet AnswersCD { get { return dsAnswersCD; } }
public int LenQuery { get { return lenQuery; } }
public int LenComment { get { return lenComment; } }
public int LenAnswer { get { return lenAnswer; } }
public int CountTicket { set { countTicket = value; } }
public void Execute(SelectTicket selectTicket)
{
string path = System.IO.Directory.GetCurrentDirectory();
int len;
switch (selectTicket)
{
case SelectTicket.AB:
// Заполняем DataSet
FillDataset(path + @"\AB", dsQuestionsAB, dsAnswersAB, countTicket);
// Считаем размеры полей для создания объектных таблиц
foreach (DataRow row in dsQuestionsAB.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersAB.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
break;
case SelectTicket.CD:
// Заполняем DataSet
FillDataset(path + @"\CD", dsQuestionsCD, dsAnswersCD, countTicket);
// Считаем размеры полей для создания объектных таблиц
foreach (DataRow row in dsQuestionsCD.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersCD.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
break;
case SelectTicket.ABCD:
// Заполняем DataSet
FillDataset(path + @"\AB", dsQuestionsAB, dsAnswersAB, countTicket);
FillDataset(path + @"\CD", dsQuestionsCD, dsAnswersCD, countTicket);
// Считаем размеры полей для создания объектных таблиц
foreach (DataRow row in dsQuestionsAB.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersAB.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
foreach (DataRow row in dsQuestionsCD.Tables[0].Rows)
{
len = row["Query"].ToString().Length;
if (len > lenQuery)
lenQuery = len;
len = row["Comment"].ToString().Length;
if (len > lenComment)
lenComment = len;
}
foreach (DataRow row in dsAnswersCD.Tables[0].Rows)
{
len = row["Answer"].ToString().Length;
if (len > lenAnswer)
lenAnswer = len;
}
break;
}
}
public void Show(DataSet dsQuestions, DataSet dsAnswers)
{
for (int i = 0; i < dsQuestions.Tables.Count; i++)
{
// Распечатываем заголовки столбцов
foreach (DataColumn col in dsQuestions.Tables[i].Columns)
Console.WriteLine("{0}", col.ColumnName);
Console.WriteLine();
// Распечатываем строки
foreach (DataRow row in dsQuestions.Tables[i].Rows)
{
foreach (DataColumn col in dsQuestions.Tables[i].Columns)
Console.WriteLine("{0}", row[col].ToString());
Console.WriteLine();
}
Console.WriteLine();
}
for (int i = 0; i < dsAnswers.Tables.Count; i++)
{
// Распечатываем заголовки столбцов
foreach (DataColumn col in dsAnswers.Tables[i].Columns)
Console.WriteLine("{0}", col.ColumnName);
Console.WriteLine();
// Распечатываем строки
foreach (DataRow row in dsAnswers.Tables[i].Rows)
{
foreach (DataColumn col in dsAnswers.Tables[i].Columns)
Console.WriteLine("{0}", row[col].ToString());
Console.WriteLine();
}
Console.WriteLine();
}
}
}
}
При создании экземпляра этого класса в клиенте должен быть произведен разбор кода HTML -таблиц и заполнены 4 (если режим SelectTicket.ABCD ) набора данных типа DataSet, ссылки на которые доступны через свойства QuestionsAB, AnswersAB, QuestionsCD, AnswersCD. Попутно будут вычислены максимальные размеры полей LenQuery, LenComment, LenAnswer.
Модифицируйте файл Program.cs следующим образомusing System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace CreateDataSet
{
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.White;
Console.WindowWidth = 81;
CreateData tables = new CreateData();
tables.CountTicket = 1;
tables.Execute(SelectTicket.CD);
tables.Show(tables.QuestionsCD, tables.AnswersCD);
Console.WriteLine(String.Format("LenQuery={0}; LenComment={1}; LenAnswer={2}",
tables.LenQuery, tables.LenComment, tables.LenAnswer));
Console.ReadLine();
}
}
}
Через панель Solution Explorer создайте в корне текущего проекта папку CD и скопируйте в нее контекстной командой Add/Existing Item из прилагаемого к работе каталога Source файл Ticket1.htm
В Solution Explorer выделите файл Ticket1.htm и в панели Properties назначьте ему свойство
Запустите проект на выполнение - получится следующий консольный выводQueryID Query CorrectAnswer Comment Theme 1_4 Каким транспортным средствам в обозначенной знаком зоне запрещена стоянка в выходные и праздничные дни ? 1 Знак 5.27 "Зона с ограничением стоянки" относится к зональным знакам, требования которых действуют на всей территории (участке дороги), обозначенной такими знаками, вплоть до выезда из зоны, обозначенного в данном случае знаком 5.28 "Конец зоны с ограничением стоянки". Табличка 8.5.1, размещенная под знаком, распространяет его действие только на субботние, воскресные и праздничные дни. А изображение таблички 8.4.1 "Вид транспортного средства" в нижней части знака информирует о том, что в обозначенной зоне стоянка запрещена только грузовым автомобилям, у которых разрешенная максимальная масса превышает 3,5 т. 4 1_9 По какой траектории Вы имеете право выполнить разворот, управляя автопоездом, имеющим большую длину ? 1 При недостаточной для разворота ширине проезжей части Правила разрешают вне перекрестка выполнение маневра не из крайнего левого положения, а с обочины или от правого края проезжей части (п. 8.8), т.е. по траектории А. 8 1_10 С какой скоростью Вы можете продолжить движение вне населенного пункта по левой полосе на грузовом автомобиле с С разрешенной максимальной массой более 3,5 т ? 3 Знак 4.6 "Ограничение минимальной скорости" и табличка 8.14 "Полоса движения" предписывают двигаться по левой полосе со скоростью не менее 50 км/ч. Однако при этом на дороге вне населенного пункта, не относящейся к автомагистрали, вы не имеете права развивать скорость на грузовом автомобиле более 70 км/ч (п. 10.3). 10 QueryID AnswerID Answer 1_4 1 Только грузовым автомобилям с разрешенной максимальной массой более 3,5 т. 1_4 2 Всем грузовым автомобилям. 1_4 3 Всем транспортным средствам. 1_9 1 Только по А. 1_9 2 Только по Б. 1_9 3 По любой. 1_10 1 Не более 50 км/ч. 1_10 2 Не менее 50 км/ч и не более 90 км/ч. 1_10 3 Не менее 50 км/ч и не более 70 км/ч. LenQuery=157; LenComment=615; LenAnswer=74
Мы видим, что данные извлекаются утилитой из HTML -таблицы правильно и размещаются построчно в соответствующих полях объекта DataSet.
В заключение приведем интересный способ регулирования ширины окна WPF, приведенный в http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/a4b5150f-5d70-4d33-9355-4cac9521677d
Создайте приложение WPF Application с именем WpfApplication1, внесите в него приведенный ниже код и испытайте работу<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Name="window1" MouseWheel="Window_MouseWheel">
<Grid>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
namespace WpfApplication1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
{
int delta = e.Delta;
double w1 = this.window1.ActualWidth;
double w2 = w1 + 2 * delta / 10;
DoubleAnimation anima = new DoubleAnimation();
anima.Duration = new Duration(TimeSpan.FromSeconds(0.3));
anima.From = w1;
anima.To = w2;
anima.FillBehavior = FillBehavior.HoldEnd;
this.window1.BeginAnimation(Window.WidthProperty, anima);
}
}
}
Внимательно разберитесь со всем приведенным кодом упражнений. Такой подход - самый короткий путь научиться программировать
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.