Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.82/11: Рейтинг темы: голосов - 11, средняя оценка - 4.82
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3

Синхронизация Модели с UI потоком

09.01.2024, 12:06. Показов 2471. Ответов 43
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Тема создана разделением исходной: Как определить из кода библиотеки Standard платформу приложения (Forms, WPF, UWP, Framework, Core и т.д.)?

Немного нубский вопрос -- насколько плохо считается прокидывать Dispatcher в модель/команду? Я просто сейчас для себя пишу WPF приложуху, и для отслеживания изменения не из UI потока прям в модель запихиваю диспетчер из окна/контролера. Я так понимаю желательно максимально отвязывать от UI модели/команды, но не особо представляю как это сделать малой кровью.
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
09.01.2024, 12:06
Ответы с готовыми решениями:

Модуль EVO II синхронизация 50гц, На какой ножке контроллера синхронизация шим двигателя?
Частый дефект для модулей EVO II с коллекторным двигателем - нет управления двигателем при помехах в сети. Помехи создают диммеры, блоки...

Как обратиться к исходной модели моего класса из прокси-модели в pyqt5?
У меня есть модель my_model класса MyModelClass(QAbstractTableModel), у которой есть переменная класса columns. Я подключаю эту модель к...

Как создать скелет для stl модели по точкам поверхности модели?
нужно придумать алгоритм, чтобы автоматически создавался скелет для stl модели по точкам поверхности модели. Перерыл весь интернет (как мне...

43
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
10.01.2024, 17:10
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Меня больше интересует другое.
Потому я и делаю как показал выше. Тогда 100% контекст будет именно того окна, который мне в этот момент нужен. Да и во всех примерах со стандартной реализацией, которые я видел, именно такой подход и показан. Загрузилось окно -> захватили контекст -> работаем с коллекциями... Просто в CB окна пара лишних строк и одно свойство в VM
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
10.01.2024, 18:19  [ТС]
Элд Хасп, писал по памяти. WPF, соотвественно CanExecuteChanged и PropertyChanged.

Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Wolfdp, Если честно не очень понял что вы пытаетесь сделать...
проблема сводится к тому что из не UI потока обновляется модель, которая через INotifyPropertyChanged должна обновлять привязки на UI.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.01.2024, 19:38
Цитата Сообщение от Wolfdp Посмотреть сообщение
писал по памяти. WPF, соотвественно CanExecuteChanged и PropertyChanged.
Для WPF не нужно маршалинга PropertyChanged в UI поток.
Там нужен маршалинг CollectionChanged.

Добавлено через 3 минуты
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
захватили контекст -> работаем с коллекциями..
Здесь задача более общая.
Если работа с коллекциями в разных потоках, то нужно обеспечить потокобезопасную работу с ними. А решение синхронизации с GUI - это незначительная часть этой потокобезопасности.
Поэтому, лично я, считаю плохой практикой использование UI потока для этого.
Только в каких-то крайних случаях и с пониманием, что это костыль.
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
11.01.2024, 07:01  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Для WPF не нужно маршалинга PropertyChanged в UI поток.
хм... по ходу да -- тупанул. Кажись всё решается проще -- просто в фабрике элементов, создавать все UI объекты в нужном потоке. Сейчас набрал пример без IoC -- все работает без ошибок. Надо будет ковырнуть свою фабрику.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
11.01.2024, 08:25
Цитата Сообщение от Wolfdp Посмотреть сообщение
Кажись всё решается проще -- просто в фабрике элементов, создавать все UI объекты в нужном потоке.
Ещё проще.
Все UI элементы должны создаваться в XAML (Content, Template, DataTemplate и пр.) и никаких "фабрик UI элементов" не нужно. Шаблоны это и есть их фабрики.

Добавлено через 8 минут
Для "стандартного" WPF (так же и для Форм) UI поток - это основной поток приложения и он не меняется всё время сеанса приложения. Поэтому все теоретические проблемы, о которых я писал выше, там не возникают. Иначе я бы давно создал реализацию с их учётом.

Проблемы возникают, когда "отклоняются от стандарта": вызывают WPF окно из Форм; искусственно создают несколько корневых UI потоков (тех в которых открываются окна); многоплатформенные GUI-приложения и прочее.
Так как нет чётко описания в документации связанности UI элементов с единственным потоком, то в теории возможно реализовать это как угодно. Вот учёт этого "как угодно" и вызывает проблемы.
Насколько помню, только у Форм по документации Контекст Синхронизации связан с потоком 1 к 1.
0
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
11.01.2024, 08:49
Цитата Сообщение от Wolfdp Посмотреть сообщение
проблема сводится к тому что из не UI потока обновляется модель, которая через INotifyPropertyChanged должна обновлять привязки на UI.
А причём тут модель и INPC? INPC реализуется во ViewModel для обновления UI и ещё чего-нибудь. Именно VM готовит данные для отображения, модели вообще фиолетово куда и кому эти данные улетают и кто ими пользуется.
Паттерн MVVM:
1. Model - бизнес-логика, слой работает сам по себе и не знает вообще ни о чём кроме себя самого и классов DTO.
2. ViewModel - слой для связи Model и View, знает только о Model и о классах DTO.
3. View - слой приложения с UI, в 90% случаев сделан совместно с Application, знает только про ViewModel и классы DTO.
4. Services - всякое вспомогательное нечто, которое через DI внедряется куда надо, не знает вообще ни о чём.
5. Application - связывает все слои вместе с помощью DI или ещё чего-нибудь, включает платформозависимые сервисы и т.д.
Вот такая жесткая иерархия и должна быть у вас. Пункты 1, 2, 3 и 4 - разные проекты в одном решении. И тогда никакого проникновения логики вышестоящего слоя в нижестоящий не будет.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
11.01.2024, 09:20
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
А причём тут модель и INPC? INPC реализуется во ViewModel для обновления UI и ещё чего-нибудь.
1) INPC может быть реализован и на уровне Модели. Этот интерфейс объявлен в сборке System.ObjectModel.dll и относится к типам "общего применения". И то, что он дефолтный для Привязок в WPF не значит, что его применение следует ограничить на этом уровне. Но согласен, что на уровне Модели обычно удобнее применять другие интерфейсы, чаще с кастомными событиями.

2) Использование в VM этого интерфейса не гарантирует, что его событие будет вызываться только в UI потоке. Скорее даже в типичной реализации это событие чаще будет вызываться асинхронно: Модель обновилась асинхронно -> подняла также асинхронно своё кастомное событие -> в этом же потоке VM обновляет своё состояние -> VM подымает асинхронно событие INPC.

Добавлено через 2 минуты
Это же относится и к событиям интерфейсов ICommand, INotifyCollectionChanged, IBindingList и, возможно, DataTable.

Добавлено через 3 минуты
Тот же INotifyCollectionChanged (ObservableCollection), например, используется в EF. А это уровень даже не Модели, а Репозитория.
1
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
11.01.2024, 09:21
Wolfdp, И ещё по типам проектов, чтоб вообще всё по правильному было
Пункты те же
1. NET Standard
2. В идеале или при использовании сторонних пакетов (ReactiveUI, CommunityToolkit) - NET Standard, если писать только под WPF, использование CommandManager (RelayCommand), то тип Библиотека NET с поддержкой PresentationFramework. Единственный используемый тип из этой библиотеки - CommandManager в реализации RelayCommand().
3 + 5. Приложение WPF
4. NET Standard
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
11.01.2024, 10:34  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Все UI элементы должны создаваться в XAML
хм... окей, возможно моя идея с UIElementFactory действительно не рабочая. Попробую заменить логику так, чтобы просто менять модель в свойстве, и подтягивалась нужная (я так понял можно указывать какой темплейт подтягивать в зависимости от типа данных).

Сейчас накидал вот вообще по минимуму кода чтобы проверить что к чему -- вроде остается проблема с ICommand -- если команду создаю вне UI потока, то потом валится на CanExecuteChanged. Для команд таки придется прокидывать маршалинг (или как оно правильно называется)?

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Window x:Class="Nya.CheckThread.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="{Binding CurrentTime}" Height="450" Width="800">
    <Grid>
        <Button Content="Button"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Height="47"
                Width="128"
                Command="{Binding GetTime}"
                />
 
    </Grid>
</Window>
C#
1
2
3
4
5
6
7
8
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = Task.Factory.StartNew(() => new UIModel()).Result;
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UIModel : INotifyPropertyChanged
{
    private string currentTime = "none";
    private UICommand uiCommand;
 
    public UIModel()
        => uiCommand = new(this);
 
    public string CurrentTime
    {
        get => currentTime;
        set
        {
            currentTime = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
        }
    }
 
    public event PropertyChangedEventHandler? PropertyChanged;
 
    public ICommand GetTime => uiCommand;
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class UICommand : ICommand
{
    private readonly UIModel _model;
    private Task? task;
 
 
    public event EventHandler? CanExecuteChanged;
 
    public UICommand(UIModel model)
        => _model = model;
 
    public bool CanExecute(object? parameter)
        => task is null;
 
    public void Execute(object? parameter)
    {
        task = Task.Run(async () => 
        {
            try
            {
                var i1 = Environment.CurrentManagedThreadId;
                await Task.Delay(TimeSpan.FromSeconds(2));
                var i2 = Environment.CurrentManagedThreadId;
                _model.CurrentTime = $"[{i1} ; {i2}] = {DateTime.Now}";
                task = null;
 
                CanExecuteChanged?.Invoke(this, EventArgs.Empty); // $exception {"The calling thread cannot access this object because a different thread owns it."} System.InvalidOperationException
            }
            catch
            {
                ;
            }
        });
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}
0
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
11.01.2024, 10:47
Wolfdp, Попробуйте вот такую реализацию - MVVM - Going async with async command
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
11.01.2024, 12:51  [ТС]
Andrey-MSK, насколько вижу там используется MvvmLight, который в nuget manager имеет пометку This package has been deprecated as it is legacy and no longer maintained.
0
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
11.01.2024, 14:03
Цитата Сообщение от Wolfdp Посмотреть сообщение
насколько вижу там используется MvvmLight
Там общая реализация по интерфейсу.
We are not forced to use a RelayCommand and we can craft our own ICommand implementation:
Добавлено через 1 минуту
Wolfdp, На GitHub есть исходный код, там ни слова про сторонние библиотеки.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
11.01.2024, 15:02
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Попробуйте вот такую реализацию - MVVM - Going async with async command
Это, вообще, о другом - об асинхронном исполнении. А речь за маршалинг CanExecuteChanged.

Цитата Сообщение от Wolfdp Посмотреть сообщение
вроде остается проблема с ICommand -- если команду создаю вне UI потока, то потом валится на CanExecuteChanged
Всё верно.
Решать надо либо через захват контекста потоков слушателей - ссылку на мою реализацию я давал выше.
Либо через маршалинг внутри View. Реализовать его можно разными способами. Но все они непросты...
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
11.01.2024, 15:04  [ТС]
Andrey-MSK, честно говоря не понял задума по IAsyncCommand -- просто пинается таска и по сути все. Более того, этот метод выглядит неправильным: мы меняем статус, выполняем задачу (т.е. дожидаемся завершения), опять меняем статус и только тогда пинаем ивент. По сути UI узнает об изменениях только после завершения.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public async Task ExecuteAsync()
    {
        if (CanExecute())
        {
            try
            {
                _isExecuting = true; // вот кажись тут явно нужен вызов RaiseCanExecuteChanged
                await _execute();
            }
            finally
            {
                _isExecuting = false;
            }
        }
 
        RaiseCanExecuteChanged();
    }
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
11.01.2024, 15:09
Цитата Сообщение от Wolfdp Посмотреть сообщение
public UICommand(UIModel model)
        => _model = model;
Треш!!!
0
Эксперт .NET
 Аватар для Wolfdp
3789 / 1766 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
11.01.2024, 15:10  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Решать надо либо через захват контекста потоков слушателей
ок, пока остановлюсь на этом. всем спасибо за участие.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
11.01.2024, 15:10
Цитата Сообщение от Wolfdp Посмотреть сообщение
насколько вижу там используется MvvmLight, который в nuget manager имеет пометку This package has been deprecated as it is legacy and no longer maintained.
Для обучения он вполне хорош по прежнему.
И скорее всего так же до сих пор лидер по загрузкам.
0
11.01.2024, 15:12  [ТС]

Не по теме:

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Треш!!!
возможно. Менять это 100% у себя не буду, смысла сейчас делать по другому нет.

0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
11.01.2024, 15:28
Цитата Сообщение от Wolfdp Посмотреть сообщение
остановлюсь на этом.
Обновлю ссылку на Реализацию Команд с захватом контекста синхронизации потоков слушателей: Исполнение делегатов обобщённого типа

Эти команды работают во всех стандартных приложениях: формы, WPF, UWP, Avalonia и др.
В приложениях с "перекрёстными" платформами (например, из Форм вызывается WPF окно) - не тестировал.

Сам базовый класс для событий можно использовать с любым типом событий: PropertyChanged, CollectionChanged и прочими.

Добавлено через 1 минуту
Цитата Сообщение от Wolfdp Посмотреть сообщение
Менять это 100% у себя не буду, смысла сейчас делать по другому нет.
Надо!

Команда должна инициализироваться передачей в неё одного или двух делегатов, а не Модели.

Добавлено через 6 минут
"Стандартная" простейшая реализация команд с МЕТАНИТ:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Windows.Input;
 
namespace MVVM
{
    public class RelayCommand : ICommand
    {
        private Action<object> execute;
        private Func<object, bool> canExecute;
 
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
 
        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            this.execute = execute;
            this.canExecute = canExecute;
        }
 
        public bool CanExecute(object parameter)
        {
            return this.canExecute == null || this.canExecute(parameter);
        }
 
        public void Execute(object parameter)
        {
            this.execute(parameter);
        }
    }
}
И её использование:
C#
13
14
15
16
17
18
19
20
21
22
23
24
25
        public RelayCommand AddCommand
        {
            get
            {
                return addCommand ??
                  (addCommand = new RelayCommand(obj =>
                  {
                      Phone phone = new Phone();
                      Phones.Insert(0, phone);
                      SelectedPhone = phone;
                  }));
            }
        }
Можно упростить в современном Шарпе:
C#
13
14
15
16
17
18
        public RelayCommand AddCommand =>addCommand ??= new RelayCommand(obj =>
        {
            Phone phone = new Phone();
            Phones.Insert(0, phone);
            SelectedPhone = phone;
         });
Добавлено через 4 минуты
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
есть исходный код, там ни слова про сторонние библиотеки.
В MvvmLight есть своя реализация команд, но он позволяет использовать и любую другую, из любой другой сторонней библиотеки.
Нет жёсткой завязки именно на свою реализацию. Именно за это здесь и написано.
0
11.01.2024, 15:32  [ТС]

Не по теме:

Элд Хасп, я таки пожалуй отложу это в долгий ящик. Хочется собрать хотя бы относительно рабочий билд и занятся обработчиком сообщений, а не доводить до идеала UI. Может потом закину для модели интерфейс с методами, если вдруг таки надумаю писать тесты для всего этого.

0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
11.01.2024, 15:32
Помогаю со студенческими работами здесь

При выборе из Ad модели выбрать по кол-ву в связанной модели ad_images
Всем привет В laravel 6 с фильтром из таблицы ads и хочу установить условие чтобы возвращались только с существующими каритинками в...

Выбор в таблице данных модели ссылки на экземпляр связанной модели
Есть ли какой-то автоматизированный способ вывести на страницу таблицу с записями модели, одно из полей которой ссылается на связанную...

Как вытащить значение поля модели внутри самой модели
Здравствуйте. При написании моего проекта возник следующий вопрос. Имеется модель: class Candidate(models.Model): ...

Как использовать представление одной модели в представлении другой модели?
Добрый день! Хотел бы узнать как использовать представление одной модели в представлении другой модели? В Yii2 совсем новичок. ...

Скрыть все 3д модели или их части что находятся за границами другой модели
Есть стекло, за ним находиться 3д модель персонажа почти в притык, и надо что бы если персонаж будет вылезать например в право или в лемо...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru