Ответы пользователя по тегу C#
  • Как поместить 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
    Ответ написан
    5 комментариев
  • Как сделать 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, ...) то я бы посоветовал сразу начать с практики углубляя знания по мере необходимости.

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

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

    FoggyFinder
    @FoggyFinder
    public List<string> GetPhotoUrl() => 
                Api.Wall
                   .Get(new WallGetParams { Domain = "lol.community", Count = 2 })
                   .WallPosts
                   .Select(item => (item.Attachments[0].Instance as Photo).Sizes[0].Url.AbsoluteUri.ToString())
                   .ToList();
    Ответ написан
    Комментировать
  • Как отследить нажатую клавишу в mvvm?

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

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

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

    Измените свойство EditingMode на подходящий для вас формат - установите его равным EraseByPoint или EraseByStroke
    Ответ написан
    Комментировать
  • Как проверить были ли все кликнуты элементы?

    FoggyFinder
    @FoggyFinder
    Рассматривайте "было ли нажатие" как состояние.

    Создайте новый, отдельный класс, который будет содержат все дополнительные состояния, свойства объекта для более удобной с ним работы:

    public class SomeObject
    {
        public bool WasClicked { get; set; }
        public MapObject2 Object { get; }
    
        public SomeObject(MapObject2 mobj)
        {
            Object = new MapObject2(mobj);
        }
    }


    Внимание - не используйте название SomeObject. Наименования должны отражать смысл, назначение объекта. Мне сложно понять цель проекта поэтому дать осмысленное название не могу. К классу MapObject2 это тоже относится.

    Есть и другие варианты, но я бы на вашем месте остановился именно на таком подходе.
    Ответ написан
  • Определить кол-во вхождений строки в строку - почему не работает?

    FoggyFinder
    @FoggyFinder
    Отбрасывая синтаксический ошибки (отсутствие точки с запятой и инициализации переменной) получаем следующую проблему:

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

    Метод IndexOf вернет -1, в том случае если поиск оказался безрезультатным.

    Очевидная проверка решает проблему:

    if (index == -1)
        break;


    Теперь к вопросу о том можно ли немного сократить код сохранив при этом подход.

    Для начала использование метода Contains здесь выглядит избыточным - метод IndexOf вполне неплохо справится с этой задачей поиска.

    Отдельная переменная length только создает лишний шум - длина подстроки остается неизменной.

    Кроме того вместо метода Remove можно использовать метод Substring. Здесь большой разницы в использовании между ними нет, то лично мне кажется что название Substring лучше отражает суть происходящего. Но тут уже дело вкуса.

    Напоследок - в C# принято называть методы с заглавной буквы, лучше соблюдать принятые соглашение об именовании.

    Итого:

    static int StringCounter(string s1, string s2)
    {
        int count = 0;
        int index;
        while (true)
        {
            index = s1.IndexOf(s2);
            if (index == -1)
                return count;
            s1 = s1.Substring(index + s2.Length);
            count++;
        }
    }
    Ответ написан
    33 комментария
  • Можно ли комментировать локальные переменные Visual Studio [нельзя]?

    FoggyFinder
    @FoggyFinder
    Если вы имеете ввиду xml-документацию, то нельзя.

    Локальные переменные - детали реализации. Старайтесь давать самодокументируемые названия, а сложные участки кода дополнительно сопровождайте комментариями.
    Ответ написан
    3 комментария
  • Как решить проблему?

    FoggyFinder
    @FoggyFinder
    В таких случаях первое что нужно сделать это проверить наличие подписки на событие.

    В IDE Visual Studio убедиться можно в конструкторе (ПКМ по элементу -> "Свойства", затем вкладка "События") и ищете в списке требуемое. Там же можно и подписаться.

    Или добавить подписку самостоятельно в конструкторе:

    button1.Click += button1_Click;
    button2.Click += button2_Click;
    Ответ написан
    1 комментарий
  • Из за чего ен отображаются изображения в 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.

    Это очень важные интерфейсы, поэтому рекомендую разобраться как они работают.
    Ответ написан
    2 комментария
  • C# По нажатию на кнопку запускается start.bat, путь где находится start.bat считывается из Settings.xml, Как это реализовать?

    FoggyFinder
    @FoggyFinder
    Для чтения информации из XML вы можете использовать Linq2Xml, XmlSerializer, XmlDocument или что-то другое.

    В вашем случае, на мой взгляд, лучше подойдет Linq2Xml так как исходный файл маленький и информацию из него нужно извлекать всякий раз по запросу.

    На русском языке есть онлайн туториал:

    Выборка элементов в LINQ to XML
    Общие сведения о LINQ to XML (C#)

    Приведенный в вопросе xml не валиден - он содержит более одного корневого элемента (Settings), добавьте к нему root:

    <?xml version="1.0" encoding="UTF-8"?>
    <root>
       <Settings>
          <PathToFile>"C:\Users\1234\Desktop\start.bat"</PathToFile>
       </Settings>
       <Settings>
          <PathToFile>"C:\Users\1234\Desktop\start2.bat"</PathToFile>
       </Settings>
    </root>


    Не могу сказать что мне нравится такая структура xml, но, по крайней мере, теперь с ним можно работать.

    Для чтения пути:

    var path = "Settings.xml"; // путь к файлу настроек
    var batName = "start.bat";
    
    var xdoc = XDocument.Load(path); 
    var batPath =
        xdoc
        .Descendants("PathToFile") // ищем элементы PathToFile
        .SingleOrDefault(xe => xe.Value.Contains(batName)); // ищем единственный из них содержащий нужный bat файл
    
    if (batPath == null) // если в файле нет нужного пути
    {
        MessageBox.Show("Путь не указан"); // сообщаем пользователю
    }
    else // иначе
    {
        Process.Start(batPath.Value.Trim('"')); // запускаем процесс
    }
    Ответ написан
  • Как обработать событие в рамках MVVM через Interactions.Triggers у подключенной библиотеки?

    FoggyFinder
    @FoggyFinder
    Написать свой триггер для учета прикрепляемых событий. Как, например, сделано тут:

    https://sergecalderara.wordpress.com/2012/08/23/ho...

    или тут
    Ответ написан
    3 комментария
  • Как правильно указать 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.
    Ответ написан
    2 комментария
  • Как вытащить данные в формате json с помощью newtonsoft?

    FoggyFinder
    @FoggyFinder
    Ваш json - массив, а значит вместо JObject нужно использовать JArray.

    Дальше, если вы уверены что значение name для которого вы хотите узнать значение count существует и единственное используйте Single. Для извлечения значения определенного типа есть метод Value

    var test = JArray.Parse(json);
    var count = // count = 125
        test
        .Single(j => j.Value<string>("name") == "facebook")
        .Value<int>("count");


    Но если подобных поисков у вас будет много, то я все-же советую воспользоваться предложением VoidVolker и сгенерировать классы для более удобной работы. Например, преобразовать в словарь с ключами - name и значениями SomeItem.
    Ответ написан
    2 комментария
  • Какое новое ключевое слово в C# для иммутабельных классов?

    FoggyFinder
    @FoggyFinder
    То, о чем вы сейчас говорите, называется Records (записи), давно и успешное используемое в F#.
    Вопрос о том когда будет добавлена их поддержка в C# до сих пор открыт. Судя по всему в C# 8.0 не войдет. А дальше будет видно, так как слухи ходили еще со времен C# 6.0.

    В предложении фигурирует "with-expressions" которое позволит писать следующий код:

    e1 with { identifier = e2, ... }

    и будет равносильно

    e1.With(identifier2: e2, ...)

    Более подробную информацию вы можете узнать тут
    Ответ написан
    4 комментария
  • Как динамически менять значение TextBox, если Binding идет через Dictionary?

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

    OnPropertyChanged(nameof(TextBoxes));

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

    OnPropertyChanged();

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

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


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

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

    FoggyFinder
    @FoggyFinder
    Предположим, что нужно обрабатывать набор некоторых данных представляемых таблицей. У каждой таблицы есть заголовок, пользователь может добавлять произвольное число таблиц и редактировать данные в них. В качестве примера обработки возьмем простую сумму чисел.

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

    public class SomeClass
    {
        public int M { get { return Data.GetLength(1); } }
        public int N { get { return Data.GetLength(0); } }
    
        public double[,] Data { get; }
    
        public SomeClass(double[,] data)
        {
            Data = new double[data.GetLength(0), data.GetLength(1)];
            Array.Copy(data, Data, data.Length);
        }
    
        public double Sum()
        {
            var s = 0.0;
            foreach (var x in Data)
                s += x;
            return s;
        }
    }


    и класс Item, который отвечает за таблицу. Тут все просто - таблица это сами данные и заголовок.

    public class Item
    {
        public string Title { get; }
        public SomeClass Data { get; }
    
        public Item(string title, SomeClass sc)
        {
            Title = title;
            if (sc != null)
                Data = new SomeClass(sc.Data);
        }
    
        public override string ToString() => Title;
    }


    Теперь представление для ячейки (одной таблицы). В таких случаях удобно создавать UserControl, назовем его ItemView

    Определим в нем два метода - Fill и Extract.

    Fill - отвечает за то чтобы отобразить указанный элемент.
    Extract - соберет информацию из элементов управления и вернет экземпляр класса Item.

    Таблицу можно описать в виде следующих элементов управления - двух TextBox для указания размерности (количество строк, столбцов) и DataGridView для заполнения значений.

    public void Fill(Item item)
    {
        label1.Text = item.Title;
        if (item.Data != null)
        {
            var n = item.Data.N;
            var m = item.Data.M;
            textBox1.Text = n.ToString();
            textBox2.Text = m.ToString();
    
            dataGridView1.ColumnCount = m;
            dataGridView1.RowCount = n;
    
            for (int i = 0; i < n; i++)
                for (int j = 0; j < m; j++)
                    dataGridView1[j, i].Value = item.Data.Data[i, j];
        }
        else
        {
            textBox1.Text = "";
            textBox2.Text = "";
    
            dataGridView1.ColumnCount = 0;
            dataGridView1.RowCount = 0;
        }
    }


    и реализация метода Extract:

    public Item Extract()
    {
        string title = label1.Text;
        if (string.IsNullOrWhiteSpace(title))
            return null;
    
        var n = dataGridView1.RowCount;
        var m = dataGridView1.ColumnCount;
    
        if (n == 0 || m == 0)
            return new Item(title, null);
    
        var data = new double[n, m];
    
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                data[i, j] = Convert.ToDouble(dataGridView1[j, i].Value);
    
        return new Item(title, new SomeClass(data));
    }


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

    Теперь практически все есть, можно переходить к созданию главной формы.
    Здесь понадобится ListBox для навигации между таблицами, панель, в которой будет размещаться UserControl, кнопка для расчетов и Label для вывода результатов.

    Чтобы связать модель с представлением воспользуемся классов BindingList:

    ItemView iview;
    BindingSource bs = new BindingSource()
    { DataSource = typeof(Item) };
    
    public Form1()
    {
        InitializeComponent();
        listBox1.DataSource = bs;
        bs.PositionChanged += PositionChanged;
    }


    PositionChanged - обработчик переключений между таблицами. Здесь нам нужно сохранять информацию в таблицу по старом индексу и отображать данные для новой.

    private void SaveCurrent()
    {
        var pr = iview.Extract();
        if (pr != null)
        {
            var old = bs.List.OfType<Item>().First(v => v.Title == pr.Title);
            var ind = bs.List.IndexOf(old);
            bs[ind] = pr;
        }
    }
    
    private void PositionChanged(object sender, EventArgs e)
    {
        if (bs.Current is Item item && bs.Count > 1)
        {
            SaveCurrent();
            iview.Fill(item);
        }
    }


    Здесь в методе SaveCurrent() мы последовательно выполняем следующие шаги:

    1. Извлекаем данные
    2. Ищем таблицу по указанному заголовку
    3. Ищем индекс в списке
    4. Обновляем таблицу

    и код для расчетов:

    SaveCurrent();
    var items = bs.OfType<Item>().Select(item => item.Data?.Sum() ?? -1);
    resLbl.Text = string.Join(";", items);


    Здесь тоже вызывается метод SaveCurrent() для принудительного сохранения при расчете.

    Итого, результат примерно следующий:

    5c975f2a21c91976114645.gif

    Код может быть не оптимальным, так как я в основном использую F# а не C# а также WPF а не WinForms, но главное тут суть подхода ;-)
    Ответ написан
    Комментировать