Россия, г. Санкт-Петербург |
Лабораторный практикум 1
Лабораторная работа №4. Калькулятор
Задание
Создать приложение "Калькулятор" для Windows Phone 7 с тремя возможными режимами: обычным, инженерным и режимом для программистов (перевод из одной системы счисления в другую).
Освоение
- навигация между страницами
- основные элементы управления и разметки (Grid, StackPanel, TextBox, TextBlock, Button, RadioButton)
- события
- контекст ввода
- меню приложения
Описание
Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.
Приложение будет состоять из трех окон:
- обычный режим
- инженерный
- режим для программистов
Для начала откроем файл разметки главной страницы MainPage.xaml. В самом верху страницы разметки изменим выводимое название приложения, например, на "КАЛЬКУЛЯТОР (обычный)". Заголовок страницы удалим или закомментируем с целью экономии места:
<!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="КАЛЬКУЛЯТОР (обычный)" Style="{StaticResource PhoneTextNormalStyle}"/> <!--TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/--> </StackPanel>
Разместим на данной страницы кнопки для основных математических операций. Для этого в разделе ContentPanel дополним таблицу Grid. Пусть в ней будет 4 колонки (для кнопок) и 3 строки (для текстового поля и двух рядок кнопок). Определим это в специальных тегах Grid.RowDefinitions и Grid.ColumnDefinitions:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="120" /> <RowDefinition Height="90" /> <RowDefinition Height="90" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> … </Grid>
Для позиционирования элементов управления внутри таблицы используются атрибуты Grid.Row и Grid.Column. Они хранят индексы строки и столбца родительской таблицы.
Элементы управления будем описывать на месте многоточия. Сначала опишем блок элементов для ввода/вывода значений: два TextBlock для введенного аргумента и операции и TextBox для ввода. Выравнивание в этих элементах (атрибут TextAlignment) будет по правому краю. Также определим отступы с помощью атрибута Margin. Для текстового поля определим контекст ввода – какого типа данные можно вводить. Установим атрибут InputScope="Number". При этом при активации данного текстового поля клавиатура будет принимать следующий вид Рис. 2.5 :
Также определим обработчики событий для текстового поля – изменение текста и изменения выделения. Это необходимо для того, чтобы предотвратить ввод неверных данных. Описание действий данных обработчиков будет описано ниже.
Также добавим кнопки для операций "+", "-", "*", "/", смена знака на противоположный, остаток от деления, сброс и равно. Добавим обработчики нажатий на данные кнопки.
<StackPanel Grid.ColumnSpan="4" Grid.Row="0" Grid.Column="0"> <TextBlock Name="lblArg1" Text="" TextAlignment="Right" Height="30" Margin="15,0,15,0" /> <TextBlock Name="lblArgOp" Text="" TextAlignment="Right" Height="30" Margin="15,0,15,0" /> <TextBox Name="txtEnter" Text="" TextAlignment="Right" VerticalAlignment="Bottom" InputScope="Number" Height="70" TextChanged="txtEnter_TextChanged" SelectionChanged="txtEnter_SelectionChanged" /> </StackPanel> <Button Content="+" Height="100" Width="100" Grid.Row="1" Grid.Column="0" Click="btnAdd_Click" /> <Button Content="-" Height="100" Width="100" Grid.Row="1" Grid.Column="1" Click="btnSub_Click" /> <Button Content="*" Height="100" Width="100" Grid.Row="1" Grid.Column="2" Click="btnMul_Click" /> <Button Content="/" Height="100" Width="100" Grid.Row="1" Grid.Column="3" Click="btnDiv_Click" /> <Button Content="±" Height="100" Width="100" Grid.Row="2" Grid.Column="0" Click="btnInv_Click" /> <Button Content="%" Height="100" Width="100" Grid.Row="2" Grid.Column="1" Click="btnMod_Click" /> <Button Content="C" Height="100" Width="100" Grid.Row="2" Grid.Column="2" Click="btnRes_Click" /> <Button Content="=" Height="100" Width="100" Grid.Row="2" Grid.Column="3" Click="btnEnter_Click" />
Для перехода к другим режимам калькулятора добавим кнопки в меню приложения. Для этого раскомментируем код внизу страницы разметки и немного его изменим, добавим обработчики нажатий на кнопки:
<!--Sample code showing usage of ApplicationBar--> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Инженерный" Click="Menu1_Click" /> <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Программист" Click="Menu2_Click" /> <!--shell:ApplicationBar.MenuItems> <shell:ApplicationBarMenuItem Text="MenuItem 1"/> <shell:ApplicationBarMenuItem Text="MenuItem 2"/> </shell:ApplicationBar.MenuItems--> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>
Перейдем к написанию кода для данной страницы.
Для начала в классе напишем перечисление для существующих операций и определим две переменные для хранения уже введенного первого операнда и операции:
enum Operation { ADD, SUP, MUL, DIV, MOD } private double arg1; private Operation oper;
В конструкторе проинициализируем первый операнд нулем:
// Constructor public MainPage() { InitializeComponent(); arg1 = 0d; }
Напишем функцию, которая будет считать результат. В случае деления она также будет проверять второй операнд на неравенство 0.
private double Count(double arg2) { double result = 0d; switch (oper) { case Operation.ADD: result = arg1 + arg2; break; case Operation.SUP: result = arg1 - arg2; break; case Operation.MUL: result = arg1 * arg2; break; case Operation.DIV: if (arg2 != 0) { result = arg1 / arg2; } else { MessageBox.Show("Делить на ноль нельзя."); result = double.MaxValue; } break; case Operation.MOD: result = arg1 % arg2; break; default: break; } return result; }
Напишем обработчики нажатий на кнопки. Одинаковый функционал вынесем в отдельную функцию Operation_Click().
private void Operation_Click() { //если мы вводим первый аргумент if (0d == arg1) { lblArg1.Text = txtEnter.Text; txtEnter.Text = ""; arg1 = double.Parse(lblArg1.Text); } else { //если первый аргумент уже есть if (txtEnter.Text.Length > 0) { double arg2 = double.Parse(txtEnter.Text); if ((Operation.DIV == oper) && (0 == arg2)) { MessageBox.Show("Делить на ноль нельзя."); txtEnter.Text = ""; } else { arg1 = Count(arg2); lblArg1.Text = arg1.ToString(); txtEnter.Text = ""; } } } txtEnter.Focus(); } private void btnAdd_Click(object sender, RoutedEventArgs e) { Operation_Click(); lblArgOp.Text = "+"; oper = Operation.ADD; } private void btnSub_Click(object sender, RoutedEventArgs e) { Operation_Click(); lblArgOp.Text = "-"; oper = Operation.SUP; } private void btnMul_Click(object sender, RoutedEventArgs e) { Operation_Click(); lblArgOp.Text = "*"; oper = Operation.MUL; } private void btnDiv_Click(object sender, RoutedEventArgs e) { Operation_Click(); lblArgOp.Text = "/"; oper = Operation.DIV; } private void btnInv_Click(object sender, RoutedEventArgs e) { if (txtEnter.Text.Length > 0) { txtEnter.Text = (- double.Parse(txtEnter.Text)).ToString(); } txtEnter.Focus(); } private void btnMod_Click(object sender, RoutedEventArgs e) { Operation_Click(); lblArgOp.Text = "%"; oper = Operation.MOD; } private void btnRes_Click(object sender, RoutedEventArgs e) { arg1 = 0d; lblArg1.Text = ""; lblArgOp.Text = ""; txtEnter.Text = ""; txtEnter.Focus(); } private void btnEnter_Click(object sender, RoutedEventArgs e) { if ((lblArg1.Text.Length > 0) && (lblArgOp.Text.Length > 0) && (txtEnter.Text.Length > 0)) { double arg2 = double.Parse(txtEnter.Text); if ((Operation.DIV == oper) && (0 == arg2)) { MessageBox.Show("Делить на ноль нельзя."); txtEnter.Text = ""; } else { arg1 = Count(arg2); lblArg1.Text = arg1.ToString(); lblArgOp.Text = ""; txtEnter.Text = ""; } } txtEnter.Focus(); }
Напишем обработчики событий для текстового поля. Функция txtEnter_TextChanged() будет ограничивать введенные данные 16 символами. Функция txtEnter_SelectionChanged() будет устанавливать курсор в конец текстового поля.
private void txtEnter_TextChanged(object sender, TextChangedEventArgs e) { if (txtEnter.Text.Length > 16) { txtEnter.Text = txtEnter.Text.Substring(0, 16); //установить кусор в конец txtEnter.Select(16, 0); } } private void txtEnter_SelectionChanged(object sender, RoutedEventArgs e) { //если курсор стоит не в самом конце - поставить его туда if (txtEnter.SelectionStart != txtEnter.Text.Length) txtEnter.Select(txtEnter.Text.Length, 0); }
Для обеспечения перехода между страницами добавим в самый верх директиву:
using System.Windows.Navigation;
Теперь определим обработчики нажатия на кнопки меню. В этих случаях мы должны обеспечить переход на другие страницы, при этом передать значение из текстового поля. Передача значений осуществляется по аналогии с GET-запросом из web-программирования (после адреса перехода ставится знак "?" и пишутся пары ключ-значение, разделенные символом "&"):
private void Menu1_Click(object sender, EventArgs e) { NavigationService.Navigate(new Uri("/PageEngineer.xaml?data=" + Uri.EscapeDataString(txtEnter.Text), UriKind.Relative)); } private void Menu2_Click(object sender, EventArgs e) { NavigationService.Navigate(new Uri("/PageProgrammer.xaml?data=" + Uri.EscapeDataString(txtEnter.Text), UriKind.Relative)); }
Для приема данных от других страниц переопределим метод OnNavigatedTo(). Просто смотрим, есть ли в запросе ключ data и, если есть, берем его значение:
protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); if (NavigationContext.QueryString.ContainsKey("data")) { txtEnter.Text = NavigationContext.QueryString["data"].ToString(); txtEnter.Focus(); } }
Добавим в проект еще 2 страницы: PageEngineer.xaml и PageProgrammer.xaml. Разметка в них будет выглядеть аналогичным образом. Разве что, на странице PageProgrammer.xaml вместо кнопок поместим RadioButton для систем счисления dec, bin и hex.
PageEngineer.xaml
<!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="КАЛЬКУЛЯТОР (инженерный)" Style="{StaticResource PhoneTextNormalStyle}"/> <!--TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/--> </StackPanel> <!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="120" /> <RowDefinition Height="90" /> <RowDefinition Height="90" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <StackPanel Grid.ColumnSpan="4" Grid.Row="0" Grid.Column="0"> <TextBlock Name="lblArg1" Text="" TextAlignment="Right" Height="30" Margin="15,0,15,0" /> <TextBlock Name="lblArgOp" Text="" TextAlignment="Right" Height="30" Margin="15,0,15,0" /> <TextBox Name="txtEnter" Text="" TextAlignment="Right" VerticalAlignment="Bottom" InputScope="Number" Height="70" TextChanged="txtEnter_TextChanged" SelectionChanged="txtEnter_SelectionChanged" /> </StackPanel> <Button Content="^" Height="100" Width="100" Grid.Row="1" Grid.Column="0" Click="btnDegree_Click" /> <Button Content="v" Height="100" Width="100" Grid.Row="1" Grid.Column="1" Click="btnRoot_Click" /> <Button Content="ln" Height="100" Width="100" Grid.Row="1" Grid.Column="2" Click="btnLn_Click" /> <Button Content="log" Height="100" Width="100" Grid.Row="1" Grid.Column="3" Click="btnLog_Click" /> <Button Content="sin" Height="100" Width="100" Grid.Row="2" Grid.Column="0" Click="btnSin_Click" /> <Button Content="cos" Height="100" Width="100" Grid.Row="2" Grid.Column="1" Click="btnCos_Click" /> <Button Content="C" Height="100" Width="100" Grid.Row="2" Grid.Column="2" Click="btnRes_Click" /> <Button Content="=" Height="100" Width="100" Grid.Row="2" Grid.Column="3" Click="btnEnter_Click" /> </Grid> </Grid> <!--Sample code showing usage of ApplicationBar--> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Обычный" Click="Menu1_Click" /> <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Программист" Click="Menu2_Click" /> <!--shell:ApplicationBar.MenuItems> <shell:ApplicationBarMenuItem Text="MenuItem 1"/> <shell:ApplicationBarMenuItem Text="MenuItem 2"/> </shell:ApplicationBar.MenuItems--> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>
PageProgrammer.xaml
<!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="КАЛЬКУЛЯТОР (программист)" Style="{StaticResource PhoneTextNormalStyle}"/> <!--TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/--> </StackPanel> <!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="80" /> <RowDefinition Height="70" /> <RowDefinition Height="70" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <!--StackPanel> <TextBlock Name="lblArg1" Text="" TextAlignment="Right" Height="30" Margin="15,0,15,0" /> <TextBlock Name="lblArgOp" Text="" TextAlignment="Right" Height="30" Margin="15,0,15,0" /> <TextBox Name="txtEnter" Text="" TextAlignment="Right" VerticalAlignment="Bottom" InputScope="Number" Height="70" TextChanged="txtEnter_TextChanged" SelectionChanged="txtEnter_SelectionChanged" /> </StackPanel--> <TextBox Name="txtEnter" Text="" Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Bottom" InputScope="Number" Height="70" TextChanged="txtEnter_TextChanged" SelectionChanged="txtEnter_SelectionChanged" /> <RadioButton Name="radioDec" GroupName="a1" HorizontalAlignment="Center" Content="Dec" Grid.Row="1" Grid.Column="0" IsChecked="True" Checked="radioDec_Checked" /> <RadioButton Name="radioBin" GroupName="a1" HorizontalAlignment="Center" Content="Bin" Grid.Row="2" Grid.Column="0" Checked="radioBin_Checked" /> <RadioButton Name="radioHex" GroupName="a1" HorizontalAlignment="Center" Content="Hex" Grid.Row="1" Grid.Column="1" FlowDirection="RightToLeft" Checked="radioHex_Checked" /> <Button Content="C" Height="70" Width="150" Grid.Row="2" Grid.Column="2" Click="btnRes_Click" /> </Grid> </Grid> <!--Sample code showing usage of ApplicationBar--> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Обычный" Click="Menu1_Click" /> <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Инженерный" Click="Menu2_Click" /> <!--shell:ApplicationBar.MenuItems> <shell:ApplicationBarMenuItem Text="MenuItem 1"/> <shell:ApplicationBarMenuItem Text="MenuItem 2"/> </shell:ApplicationBar.MenuItems--> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.