Вот что интересное заметил.
Ранее старались разделить — мухи отдельно, котлеты отдельно. Т.е. желали декларативное описание UI и отдельно уже код. Вот примеры
Канонически правильный, не мешаем котлеты с мухами:
IDD_MYDIALOG DIALOGEX 0, 0, 200, 150
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "My Dialog"
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "OK", IDOK, "Button", WS_TABSTOP, 50, 120, 50, 14
CONTROL "Cancel", IDCANCEL, "Button", WS_TABSTOP, 110, 120, 50, 14
END
| | XML |
| | <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Пример окна</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Нажми меня</string>
</property>
</widget>
</item>
</layout>
</widget>
<action name="actionExit">
<property name="text">
<string>Выход</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
|
| | |
| | QML |
| | import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 400
height: 300
title: "Пример на QML"
Column {
anchors.centerIn: parent
spacing: 10
TextField {
id: inputField
placeholderText: "Введите текст"
}
Button {
text: "Нажми меня"
onClicked: console.log("Текст: " + inputField.text)
}
}
}
|
| | |
— это уже переходной этап, т.к. кроме декларации — был JavaScript. Но все-же не смешивалось с основным ЯП — как то C++.
WinForms сделали полностью императивное описание, но все-же была автоматическая кодогенерация и файл с вашим кодом находился в другом файле. Весьма гибко и все-таки из коробки имеется разделение, хотя и не строгое.
Xaml же уже полностью вернулись к концепции полного разделения — был специальный XML-файл:
| | XAML |
| | <Window x:Class="ExampleApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Пример XAML UI" Height="300" Width="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Метка для ввода текста -->
<TextBlock Grid.Row="0" Grid.Column="0" Margin="10" Text="Введите текст:" VerticalAlignment="Center"/>
<!-- Текстовое поле для ввода -->
<TextBox x:Name="InputTextBox" Grid.Row="0" Grid.Column="1" Margin="10" Width="200"/>
<!-- Кнопка для подтверждения -->
<Button x:Name="SubmitButton" Grid.Row="1" Grid.ColumnSpan="2" Margin="10" Content="Отправить" Click="SubmitButton_Click"/>
<!-- Поле для отображения результата -->
<TextBlock x:Name="ResultTextBlock" Grid.Row="2" Grid.ColumnSpan="2" Margin="10" Text="Здесь будет результат" TextWrapping="Wrap"/>
</Grid>
</Window>
|
| | |
Интересно что ASP.Net WebForms — был смесью HTML и серверных тегов — все-таки позиционировался как декларативный — код на C# размещался отдельно, что поднимало на уровень выше по сравнению с тогдашним PHP:
| | ASP.NET Classic WebForms |
| | <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ContactForm.aspx.cs" Inherits="MyApp.ContactForm" %>
<!DOCTYPE html>
<html lang="en">
<head runat="server">
<meta charset="utf-8" />
<title>Contact Form</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<h2>Contact Form</h2>
<form id="form1" runat="server">
<div class="form-group">
<label for="Name">Name</label>
<asp:TextBox ID="NameTextBox" runat="server" CssClass="form-control" />
</div>
<div class="form-group">
<label for="Email">Email</label>
<asp:TextBox ID="EmailTextBox" runat="server" CssClass="form-control" />
</div>
<div class="form-group">
<label for="Message">Message</label>
<asp:TextBox ID="MessageTextBox" runat="server" TextMode="MultiLine" CssClass="form-control" />
</div>
<asp:Button ID="SubmitButton" runat="server" Text="Submit" CssClass="btn btn-primary" OnClick="SubmitButton_Click" />
</form>
</div>
</body>
</html>
|
| | |
Потом как-то стал популярен MVC везде, в т.ч. в том же PHP он начал активно использоваться, и уже все стало намного проще:
| | ASP.NET MVC (Razor) |
| | @model MyApp.Models.ContactForm
@{
ViewBag.Title = "Contact Form";
}
<h2>@ViewBag.Title</h2>
@using (Html.BeginForm("Submit", "Home", FormMethod.Post))
{
<div class="form-group">
@Html.LabelFor(m => m.Name)
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
</div>
<div class="form-group">
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
</div>
<div class="form-group">
@Html.LabelFor(m => m.Message)
@Html.TextAreaFor(m => m.Message, new { @class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Submit</button>
}
|
| | |
| | ASP.NET Blazor |
| | @page "/contact"
@using MyApp.Models
<h3>Contact Form</h3>
<EditForm Model="@contactForm" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="Name">Name</label>
<InputText id="Name" class="form-control" @bind-Value="contactForm.Name" />
</div>
<div class="form-group">
<label for="Email">Email</label>
<InputText id="Email" class="form-control" @bind-Value="contactForm.Email" />
</div>
<div class="form-group">
<label for="Message">Message</label>
<InputTextArea id="Message" class="form-control" @bind-Value="contactForm.Message" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
|
| | |
Используются XML-файлы с описанием элементов формы:
| | XML-layout |
| | <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/usernameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter your username" />
<Button
android:id="@+id/submitButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Submit" />
</LinearLayout>
|
| | |
Аналогично, использовался XML для описания:
| | Storyboard |
| | <?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIWindowScene</key>
<dict>
<key>UIWindowSceneRoleApplication</key>
<dict>
<key>UIViewController</key>
<dict>
<key>view</key>
<dict>
<key>subviews</key>
<array>
<dict>
<key>UILabel</key>
<dict>
<key>text</key>
<string>Hello, world!</string>
<key>frame</key>
<string>{{50, 50}, {200, 40}}</string>
</dict>
</dict>
<dict>
<key>UIButton</key>
<dict>
<key>title</key>
<string>Press me</string>
<key>frame</key>
<string>{{50, 120}, {200, 50}}</string>
<key>action</key>
<string>buttonPressed:</string>
</dict>
</dict>
</array>
</dict>
</dict>
</dict>
</dict>
</dict>
</dict>
</plist>
|
| | |
А теперь смотрите к чему мы опять вернулись.
| | Flutter |
| | import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Форма регистрации')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: RegistrationForm(),
),
),
);
}
}
class RegistrationForm extends StatefulWidget {
@override
_RegistrationFormState createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Имя'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Пожалуйста, введите имя';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Электронная почта'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Пожалуйста, введите email';
} else if (!RegExp(r'\S+@\S+\.\S+').hasMatch(value)) {
return 'Введите корректный email';
}
return null;
},
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Пароль'),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Пожалуйста, введите пароль';
} else if (value.length < 6) {
return 'Пароль должен содержать хотя бы 6 символов';
}
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState?.validate() ?? false) {
// Если форма валидна, отправить данные
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Форма отправлена')),
);
}
},
child: Text('Отправить'),
),
],
),
);
}
}
|
| | |
Вариант Compose Multiplatform:
| | Compose Multiplatform |
| | @Composable
fun LoginForm() {
Column {
TextField(value = username, onValueChange = { username = it }, label = { Text("Username") })
TextField(value = password, onValueChange = { password = it }, label = { Text("Password") }, visualTransformation = PasswordVisualTransformation())
Button(onClick = { /* handle login */ }) {
Text("Login")
}
}
}
|
| | |
И даже новомодный SwiftUI
| | SwiftUI |
| | import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, world!")
.font(.title)
.padding()
Button(action: {
print("Button pressed")
}) {
Text("Press me")
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
|
| | |
— это декларативно-императивный подход какой-то, когда вроде и все в декларации, но так же можно в любом месте и функцию дернуть. Причем сразу многие популярные платформы поддались этой тенденции. Т.е. все-таки отказались от концепции полного разделения мух и котлет — немножко мух теперь поддобавить можно