Ответы пользователя по тегу WPF
  • Можно ли во ViewModel узнать ширину элемента View?

    FoggyFinder
    @FoggyFinder
    Зависит от того зачем вам нужна информация о размерах UI - элементов.

    Если это часть пользовательских настроек, то хранить их на уровне ViewModel совершенно нормально и никак не противоречит паттерну.

    Но если это нужно исключительно для временной настройки UI, то и код должен находится на уровне View.

    Для вашего примера решение будет таким: добавьте обработчик события клика на кнопку и выводить информацию там.

    MVVM это не означает что в code-behind не должно быть никакого кода. Это типичная ошибка. В .xaml.cs не должно быть того, что не относится непосредственно к отображению.

    Управление анимацией, вывод вспомогательных сообщений, кастомизация пользовательских элементов управления - все это может быть там.
    Ответ написан
  • WPF ListBox как вручную отсортировать список перетаскиванием элементов мышкой?

    FoggyFinder
    @FoggyFinder
    Есть замечательная библиотека которая практически все делает за вас:

    GongSolutions.WPF.DragDrop

    Все, что вам нужно в простейших сценариях, это установить на разрешение перетаскивать ИЗ и В:

    dd:DragDrop.IsDragSource="True"
    dd:DragDrop.IsDropTarget="True"


    Обновляю ответ в связи с комментарием:

    Общая рекомендация:

    Если вы столкнулись с проблемой при использовании какой-то библиотеки, то задавайте конкретный вопрос с ней связанный, приводя минимально воспроизводимый пример. В противном случае начинается гадание на кофейной гуще.


    Привожу минимальный пример демонстрирующий что перетаскивание в пределах списка не является проблемой.

    VM:

    public class MainVM
    {
        private IEnumerable<FileInfo> GetFiles() =>
            Directory.EnumerateFiles(Directory.GetCurrentDirectory())
                        .Select(path => new FileInfo(path));
        public MainVM()
        {
            FileList = new ObservableCollection<FileInfo>(GetFiles());
        }
        public FileInfo CurrentFile { get; set; }
        public ObservableCollection<FileInfo> FileList { get; }
    }


    View:

    пространство имен:

    xmlns:dd="urn:gong-wpf-dragdrop"

    сам список:

    <ListBox
        dd:DragDrop.IsDragSource="True"
        dd:DragDrop.IsDropTarget="True"
        ItemsSource="{Binding FileList}"
        SelectedItem="{Binding CurrentFile}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>


    Гиф с результатом:

    5dee28369295b074649445.gif
    Ответ написан
  • Как динамически создавать элементы сетки Grid?

    FoggyFinder
    @FoggyFinder
    Можно установить в качестве панели UniformGrid, тогда все строки будут иметь одинаковую высоту.

    <ListBox Grid.Column="1" ItemsSource="{Binding Notes}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="1" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Title}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ListBox>


    За идею спасибо Athari
    Ответ написан
  • Как связать конкретную фигуру с выбранным элементом?

    FoggyFinder
    @FoggyFinder
    Пока нет возможности написать пример, но попробую дополнить ответ когда она появится.

    Если количество отображаемых элементов не слишком большое (в пределах 100-1000), то, вероятно, лучшим вариантом будет вынести часть информации в ViewModel (а именно, координаты расположения линий и состояние IsSelected). В вашем примере этот класс назван Model, но я рекомендую его переименовать, в дальнейшем это может сбить с толку.

    Тогда вы привяжите коллекцию к ItemsControl, где панелью будет выступать Canvas.
    Координаты привяжите индивидуально. Реагировать на изменение цвета можно будет или через триггер или через конвертер, но если вариантов всего 2, то проще, на мой взгляд, будет использовать именно триггер.

    При выделении элемента в коллекции сбрасываете состояние IsSelected предыдущего элемента в False и указанному соответственно ставите True.

    Обновлено:

    Появились несколько свободных минут, привожу пример:

    Для начала, для удобства создадим ViewModelBase, чтобы несколько раз не реализовывать интерфейс INPC:

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
        {
            if (EqualityComparer<T>.Default.Equals(storage, value))
                return false;
            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }
    }


    Теперь, как я уже писал выше, нам понадобиться отдельная ViewModel для ячейки, элемента списка:

    public class LineVM : ViewModelBase
    {
        private bool isSelected;
        public bool IsSelected
        {
            get { return isSelected; }
            set { SetProperty(ref isSelected, value); }
        }
    
        public LineVM(string name, int x, int y)
        {
            Name = name;
            X = x;
            Y = y;
        }
        public string Name { get; set; }
    
        public int X { get; private set; }
    
        public int Y { get; private set; }
    }


    Здесь нет необходимости в изменении координат во время работы программы, поэтому сеттеры оставим приватными.

    Главная ViewModel :

    public class ItemsVM : ViewModelBase
    {
        private LineVM selectedVM;
    
        public ObservableCollection<LineVM> Models { get; }
        public LineVM SelectedModel
        {
            get { return selectedVM; }
            set
            {
                if (SelectedModel != null)
                    SelectedModel.IsSelected = false;
                value.IsSelected = true;
                SetProperty(ref selectedVM, value);
            }
        }
    
        public ItemsVM()
        {
            Models = new ObservableCollection<LineVM>
            {
                new LineVM ("A", 10, 20 ),
                new LineVM ("B", 10, 40),
                new LineVM ("C", 10, 60),
                new LineVM ("D", 10, 80)
            };
        }
    }


    я не стал заниматься переименовыванием, но лучше, конечно, чтобы свойства отображали реальное назначение.

    Теперь View часть. Привожу только измененный третий столбец:

    <ItemsControl Grid.Column="2" ItemsSource="{Binding Models}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas IsItemsHost="True" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Line
                X1="0"
                X2="100"
                Y1="0"
                Y2="00">
                    <Line.Style>
                        <Style TargetType="Line">
                            <Setter Property="Stroke" Value="Blue" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsSelected}" Value="True">
                                    <Setter Property="Stroke" Value="Red" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Line.Style>
                </Line>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding X}" />
                <Setter Property="Canvas.Top" Value="{Binding Y}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>


    Тут тоже все достаточно просто - триггер реагирует на изменение свойства IsSelected и меняет цвет на красный для текущего элемента.

    И, для наглядности, оставляю гифку:

    5dd6e7e03664e626729135.gif
    Ответ написан
  • Как в WPF фильтровать двойной клик по ListView только левой кнопкой?

    FoggyFinder
    @FoggyFinder
    Можно пойти разными способами:

    1. Использовать InputBinding, а именно MouseBinding. Тут LeftDoubleClick один из вариантов отслеживаемых действий, посмотреть весь список можно тут

    2. Использовать CallMethodAction вместо InvokeCommandAction, тогда вы сможете учитывать аргументы события.
    Сигнатура метода должна быть точно такая же как если бы вы обрабатывали событие в View части.

    3. Поискать реализацию (или написать самостоятельно) EventToCommand где будет метод PassEventArgsToCommand (с корректной реализацией)
    Ответ написан
  • Как смотреть на конструктор WPF и на XML одновременно?

    FoggyFinder
    @FoggyFinder
    5d6bc7e79d2c0080809369.png

    нажмите на "|" кнопку на той панели которая обведена на скриншоте.

    Если конструктор окажется с правой стороны, то поменять их местами можно кнопкой с "Swap panes" - будет на той-же панели но вверху на центральном сплиттере.
    Ответ написан
  • Как поместить XAML элементы в C# массив, чрез цикл?

    FoggyFinder
    @FoggyFinder
    Сложность в реализации связана с выбранным вами подходом. Нужно работать с данными, а не с элементами управления.

    В вашем случае вы ожидаете от пользователя ввод какого-то текста.
    Каждому текстовому полю с правой стороны отвечает одно свойство. Для примера рассмотрим случаи наличия только одного поля для ввода. Назовем его Greeting.

    Для начала определим XAML разметку:

    <TextBox Text="{Binding Greeting, UpdateSourceTrigger=PropertyChanged}"
             TextWrapping="Wrap" />


    UpdateSourceTrigger=PropertyChanged означает что вы хотите изменять свойство Greeting всякий раз когда пользователь меняет что-то в текстовом поле.

    Теперь нужно определить класс который будет отвечать за хранения и обработку данных, назовем его SimpleVM:

    public class SimpleVM : INotifyPropertyChanged
    {
        private string greeting;
    
        public string Greeting
        {
            get { return greeting; }
            set
            {
                greeting = value;
                OnPropertyChanged(nameof(Greeting));
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        }
    }


    Интерфейс INotifyPropertyChanged необходим для того чтобы оповещать всех интересующихся об изменениях значений в свойстве объекта. В роли интересующегося здесь у нас выступает интерфейс. Всякий раз когда вы будете изменять свойство Greeting графический интерфейс будет обновлять свое представление.

    Осталось отобразить вводимое слово в виде квадратиков. Для этого прекрасно подойдет ItemsControl:

    <ItemsControl Grid.Column="0" ItemsSource="{Binding Greeting}" >
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBox IsReadOnly="True" Text="{Binding .}" 
                         Width="20" Foreground="Black" 
                         BorderThickness="1" BorderBrush="Black" Height="23" 
                         Background="{x:Null}" Margin="1, 3" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>


    И не забываем установить DataContext, для начала можно в конструкторе главного окна:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new SimpleVM();
    }


    Когда вы станет познакомитесь с MVVM немного больше вы сможете устанавливать контекст снаружи окна (как это обычно и делается).

    А пока результат получается следующим:

    5d36e9309ffef101248000.gif
    Ответ написан
  • Как сделать roadmap для полного обучения C# и wpf?

    FoggyFinder
    @FoggyFinder
    Есть два вариант:

    1. От теории к практике
    2. (Боевой режим) Сразу практика на пет проекте.

    Первый хорошо, на мой взгляд, естественно, подходит тем у кого небольшой опыт или нет никакого предыдущего опыта в десктоп разработке.

    Если выберите первый подход, то вам подойдет любая книга по WPF. Несмотря на то, что доступная литература относительно старая, а WPF скоро перейдет на .NET Core 3.0 информация в большинстве своем актуальна. Лично я начинал с книги Мэтью Макдональда - WPF: Windows Presentation Foundation в .NET 4.5 с примерами на C# 5.0 для профессионалов и должен сказать что доволен и подачей материала и степенью подробности изложения.

    Есть и онлайн ресурсы, самые известные:

    Руководство по WPF
    WPF - Windows Presentation Foundation

    Если английский язык у вас хотя-бы на уровне PreIntermediate то крайне рекомендую обратить внимание на

    2,000 Things You Should Know About WPF

    Тут рассматриваются как основы так и продвинутые редкие случаи и советы которые очень часто встречаются в real world проектах. Можете идти от одного номера к другому вот и получится своеобразный список проблем.

    И, конечно, документация

    Windows Presentation Foundation

    Если вы знаком с MVVM по другим фреймверкам (XF, Avalonia, ...) то я бы посоветовал сразу начать с практики углубляя знания по мере необходимости.

    Самый главный шаг вы уже сделали - найти ресурс (а лучше сразу несколько), где можно задавать вопросы, а также помогать другим людям с решением уже их проблем. Сами не заметите как ваш уровень прилично подрастет.

    Ну а пока желаю удачи и, надеюсь, увидимся в других темах ;)
    Ответ написан
  • Как отследить нажатую клавишу в mvvm?

    FoggyFinder
    @FoggyFinder
    Используйте KeyBinding:

    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding SearchCommand}" />
    </TextBox.InputBindings>
    Ответ написан
  • Как сделать ластик для InkCanvas?

    FoggyFinder
    @FoggyFinder
    Ластик - стандартный функционал для InkCanvas.

    Измените свойство EditingMode на подходящий для вас формат - установите его равным EraseByPoint или EraseByStroke
    Ответ написан
  • Из за чего ен отображаются изображения в ListBox?

    FoggyFinder
    @FoggyFinder
    В шаблоне элемента списка вы устанавливаете источником привязки ImageList - коллекцию BitmapImage

    <Image Width="100" Height="75" IsEnabled="False"
           Source="{Binding Path= ImageList}"  />


    У изображения нет такого свойства, и следовательно ничего не отображается. Здесь вы хотите использовать сам объект, а не какое-либо из его свойств. Просто не указывайте Path в привязке (или сообщите в явном виде используя символ .: {Binding Path = . })

    <Image Width="100" Height="75" IsEnabled="False"
           Source="{Binding}"  />


    Совет

    Image может сам подцепить картинку если использовать корректный путь к файлу, а значит можно упростить класс убрав ImageList и использовав в привязке LinksToPictures. Кроме этого можно безболезненно убрать приватные свойства (вы все равно не сможете к ним привязаться). В итоге после небольшого рефакторинга получится следующий класс:

    public class Gallery
    {
        private string directoryPath; // Путь к каталогу
        public IEnumerable<string> LinksToPictures { get; } // Названия файлов
    
        public Gallery(string directoryPath)
        {
            this. directoryPath = directoryPath;
            LinksToPictures = Directory.GetFiles(directoryPath, "*.jp*g");
        }
    }


    Примечание:

    1. Устанавливать ItemsSource для списка можно в разметке:

    ItemsSource="{Binding LinksToPictures}"

    Если вы так пробовали делать, но не видели изменений, проверьте что DataContext был установлен, на данном этапе это можно сделать так:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new Gallery("F://");
    }


    В дальнейшем вы научитесь устанавливать контекст извне окон.

    2. Если вы хотите изменять значение свойства из кода объекта (Galery) вам нужно будет реализовать интерфейс INotifyPropertyChanged (или, сокращенно INPC).

    А для того, чтобы интерфейс обновлялся при изменении последовательности элементов (удаление, добавление и т.д.), коллекция должна реализовывать интерфейс INotifyCollectionChanged (в ответах и комментариях вы можете увидеть часто используемое сокращение - INCC).
    List<_> к таким коллекциям не относится, поэтому лучше заменить на ObservableCollection.

    Это очень важные интерфейсы, поэтому рекомендую разобраться как они работают.
    Ответ написан
  • Как правильно указать DataContext в UserControl MVVM?

    FoggyFinder
    @FoggyFinder
    Проблема не с назначением DataContext для UserControl. Для связывания команды с кнопкой используется свойство Command а не Click:

    <Button Command="{Binding OpenDataBaseEditorView}">Open</Button>
    Ответ написан
  • Как изменить цвет WPF-элементов (цвет взять из файла)?

    FoggyFinder
    @FoggyFinder
    Используйте динамические ресурсы. Пример:

    1. Добавляйте в ресурсы приложения (файл App.xaml):

    <Application.Resources>
        <SolidColorBrush x:Key="solidGrayBrush" Color="Gray" />


    Будьте внимательны и не путайте ресурсы объектов WPF и ресурсы сборки.

    2. Ссылаетесь в элементе как динамический ресурс:

    <TextBlock
        Background="{DynamicResource solidGrayBrush}"
        Text="Test" />


    В качестве примера будем менять кисть на случайную по нажатию на кнопку:

    Brush[] brushes =
            typeof(Brushes)
            .GetProperties()
            .Select(p => (Brush)p.GetValue(null))
            .ToArray();
    Random r = new Random();


    и обработчик нажатия на кнопку:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var b = brushes[r.Next(brushes.Length)];
        Application.Current.Resources["solidGrayBrush"] = b;
    }


    5cab235c13a81873403570.gif
    Ответ написан
  • Как установить скаченную тему на WPF DataGrid?

    FoggyFinder
    @FoggyFinder
    Добавить в файлов ресурсов:

    <Application.Resources>
        <!--<ResourceDictionary Source="/DataGridThemes;component/ExpressionLight.xaml" />-->
        <!--<ResourceDictionary Source="/DataGridThemes;component/ExpressionDark.xaml" />-->
        <ResourceDictionary Source="/DataGridThemes;component/WhistlerBlue.xaml" />
    </Application.Resources>
    Ответ написан
  • Как связать свойство Command Parameter у Context Menu со свойством Name у TextBox, к которому это меню привязано?

    FoggyFinder
    @FoggyFinder
    Вы столкнулись с широко известной проблемой привязки в ContextMenu, ToolTip из-за того что эти элементы не являются частью "визуального" дерева (visual tree).

    В таких случаях предлагается два стандартных решениях - использовать PlacementTarget и прокси-объект. Первое тут должно подойти идеально.

    Прежде чем показать код отмечу, что вместо использования свойства Name в качестве ключа TextBox лучше использовать свойство Tag.

    То есть вместо:

    <TextBox Name="Box_110" ...>

    <TextBox Tag="Box_110" ...>

    Это не принципиальное изменение, но в большинстве ответов для подобных случаев вы встретите использование именно свойство Tag.

    С учетом этого привязка для CommandParameter будет выглядеть вот так:

    <Setter Property="CommandParameter" Value="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />


    И немного оффтопа:

    В вашем обработчике ContextMenuClick вы используете постоянное смещение равное 10 и код по сути для каждого кейса одинаков. Вы могли бы сократить код, предварительно заменив подсказку в Tag убрав оттуда префикс "Box_" чтобы получилось что-то подобное:

    public void ContextMenuClick(object param)
    {
        if (int.TryParse(Convert.ToString(param), out int v))
        {
            StampDictionary.TextBoxes[v.ToString()].BoxValue = Name;
            StampDictionary.TextBoxes[(v + 10).ToString()].BoxValue = Signature;
        }
    }


    Если все ключи в словаре числовые, то еще проще будет заменит тип ключа на int.
    Ответ написан
  • Как динамически менять значение TextBox, если Binding идет через Dictionary?

    FoggyFinder
    @FoggyFinder
    Чтобы сообщить представлению (View) об изменении значения конкретного свойства из VM нужно передать его название в виде параметра:

    OnPropertyChanged(nameof(TextBoxes));

    Непосредственно в самих свойствах вы можете увидеть что идет вызов без передачи параметров:

    OnPropertyChanged();

    Дело в том, что в типичной реализации имя свойства извлекается при помощи атрибута CallerMemberName:

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName]string prop = "")
    {
    // ...
    }


    Это не обязательно делать в set. У свойства может не быть открытого сеттера или не быть сеттера вообще. Или если свойство зависит от двух других - например, для автоматического отображения суммы значений, которые должен вводить пользователь.

    В вашем случае с TextBoxes вы не используете сеттер - его можно даже совсем убрать.
    Ответ написан
  • Как привязать данные таблицы к Combobox?

    FoggyFinder
    @FoggyFinder
    Все правильно, хотя, возможно, вам больше по душе придется стандартный ComboBox, а не
    DataGridComboBoxColumn:

    <DataGridTemplateColumn Header="Должность">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <ComboBox
                     HorizontalContentAlignment="Center"
                     ItemsSource="{Binding DataContext.Titles, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
                     SelectedItem="{Binding Title}" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>


    Будьте внимательны, в примере выше предполагается, что
    коллекция Titles находится на том же уровне (классе), что и коллекция Employee.
    Ответ написан
  • Как создать свой дизайн интерфейса в WPF?

    FoggyFinder
    @FoggyFinder
    1. "Напрямую накидывать" вероятно, нельзя. Но возможность импорта все-таки присутствует:

    How to Import Photoshop Files into WPF

    Насколько такой способ рабочий сейчас - не знаю, не проверял.

    2. Blend for Visual Studio overview

    3. Metro UI особый стиль для приложений / сайтов. Для WPF есть очень известная библиотека MahApps.Metro.

    Теперь, что касается "стилизации" - WPF в плане настройки внешнего вида элементов управления и приложений в целом очень гибкий. Можно сделать практически все. Не буду даже пытаться раскрывать тему, материалов в сети очень много, но попробую дать направление для дальнейших поисков:

    Ключевые слова:

    • Style
    • DataTemplate
    • Trigger


    Для ознакомления материал из документации:

    Styling and Templating

    А для основательного разбора рекомендую следующую литературу:

    1. WPF: Windows Presentation Foundation в .NET 4.5 с ...

    2. WPF 4. Подробное руководство


    Сам я изучал WPF по книге Макдональда, материал изложен в доступной форме, но рассмотрение подробное, а не только основы.

    Есть и онлайн ресурсы:

    1. metanit: Руководство по WPF
    2. professorweb: WPF - Windows Presentation Foundation


    Некоторые библиотеки, которые упрощают жизнь разработчику, предлагая стилизацию стандартных элементов а также дополняя часто используемыми контролами. Даже если вы не будете использовать ни один из приведенных в списке ниже вариантов, из исходников вы можете извлечь много полезной информации, так что даю ссылки на них, а не на страницы документации:



    Первые две очень широко используются.

    И напоследок небольшое отступление:

    Больше практики, а если в чем-то не уверены - спрашивайте, здесь, на тостере или где-нибудь еще, не так важно.

    В начале изучения WPF у вас может быть соблазн смешивать работу с данными (логику приложения) и часть отвечающую за внешний вид (интерфейс). Не стоит так делать, не заводите с самого начала плохих привычек, потом будет сложно переучиваться.

    В принципе, можете попробовать совместить F# c WPF, возможности code-behind нам намного-намного меньше ;-), а значит проще будет пойти понять шаблоны MVVM или даже Elmish.
    Ответ написан
  • Можно ли на WPF создавать самостоятельные приложения для частного использования?

    FoggyFinder
    @FoggyFinder
    Поняла, да не до конца. Что еще нужно, чтобы сделать цельное приложение


    Все зависит от самого приложения.

    Назовите плиз примеры из жизни для приложений, которые можно написать используя это фреймворк.


    Их очень много. Приведу несколько OpenSource приложений которые использую или использовал раньше:

    Snoop
    ScreenToGif
    MarkdownMonster
    LiteDbExplorer

    Эти приложения могут индивидуально писаться под заказ как сайты к примеру


    Могут и пишутся.
    Ответ написан