• Как экранировать строку со множеством кавычек одной ковычкой?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Проблема в том, что код пишется в текстовом формате, и компилятор должен понять, где начало и где конец строки. В некоторых языках (например, php и javascript) для строки можно использовать одинарные кавычки 'строка "с кавычками" внутри'. Но в c# так нельзя, приходится экранировать кавычки, одним из двух способов:
    string s1 = "строка \"с кучей\" ковычек";
    string s2 = @"строка ""с кучей"" ковычек";


    https://learn.microsoft.com/ru-ru/dotnet/csharp/pr...
    Ответ написан
    3 комментария
  • Подойдёт ли книга "Изучаем C# через разработку игр на Unity" для изучения c#?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Неплохая книга для "нулёвых" новичков, которые хотят делать игры в Юнити.

    В главах 1-5 рассказывают самые базовые вещи языка (переменные, ветви/циклы, методы) и самые простые вещи, связанные с классами. За эти главы научиться программированию невозможно, для них цель - дать нужные термины, с которыми потом можно идти дальше. Потом эти же вещи обрастут нужными подробностями и примерами во второй и третьей частях.

    В главах 6-9 больше автор говорит о Юнити, тоже довольно коротко, но если читатель хоть немного уже раньше ознакомился Юнити, то тут будет попроще. Хорошо то, что здесь рассказано, как писать скрипты о нужных частях игры, о которых рассказывает - движение объектов в скрипте, физика, поведение объектов, а также коротко о программировании ИИ (некоротко не получится - об этом пишут толстенные тома).

    Главы 10-12 углубляют понимание языка и добавляют новые концепции (разные коллекции, дженерики, делегаты, обработка ошибок). Тут поменьше кода и побольше теории. Но и тут каждый факт о языке подкрепляется примером.

    В общем, в книге показаны основы программирования на языке c# и его использование для создания базовых скриптов в Юнити. Некоторым минусом могу отметить, что тут автор больше учит собственно языку, а не программированию - то есть, как писать, а не почему писать так. Но такое в подобных книгах и не пишут - понимание приходит позже, с опытом.
    Ответ написан
    Комментировать
  • Где учить c# с нуля для игр?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Попробуйте вот такую книгу:
    Изучаем C# через разработку игр на Unity. 5-е издание Ферроне Харрисон
    Учат с нуля, показывают нужные важные вещи, с попыткой учить через Юнити (хотя по мне, лучше учиться с консольными приложениями).

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

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Зависит от драйверов тачскрина. Я разрабатывал такой киоск, там был простой антивандальный тач стекло, умел делать только клики (без жестов), вставлялся в киоск вместо обычного стекла впереди обычного нетактильного монитора. Я делал веб-приложение, показывал Оперой (в то время только у оперы был хороший режим киоска, из которого нельзя было выйти только мышью). На Win-приложение тоже всё заработает, это считается работа мышкой.
    Ответ написан
    Комментировать
  • Почему вьюха не видим вьюмодель в том же неймспейсе?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Да, всё верно поняли. Дизайнер XAML не совсем "дружит" с кодом - он ищет только те, которые уже скомпилированы. Видимо, дизайнер ищет классы стандартным способом - через тип Type (и т.п.), а тот ищет среди сборок (exe/dll) проекта.

    Нужно сразу скомпилировать новый тип, даже просто пустой class Test {} - и всё, будет работать. Лучше делать это пораньше - до того, как пытаетесь связать его в xaml, иначе билд не срабатывает, и приходится выпиливать его из xaml-а.
    Ответ написан
    1 комментарий
  • Как создать массив с разным количеством измерений?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    У объектов тип разный - string или string[], их невозможно поместить в один массив напрямую. Но можно сделать по другому - по сложному или по простому.

    Сложный - сделать отдельный тип, который принимает либо строку, либо коллекцию объектов такого же типа. И теперь в коллекцию таких типов можно поместить иерархично.

    А можно по простому - просто использовать массив объектов. И строка, и массив строк - объекты. Но придётся явно проверять реальный тип.
    object[] arr = new object[]
    {
        "Hello World!",
        new object[]
        {
            "Hello_1",
            "Hello_2"
        }
    };
    
    foreach (object item in arr)
    {
        if (item is string str)
        {
            Console.WriteLine(str);
        }
        else if (item is object[] subarr)
        {
            foreach (object subitem in subarr)
            {
                Console.WriteLine(subitem);
            }
        }
    }

    Если в требуемом массиве нужно всего лишь два уровня, то так будет работать. Но если нужно сделать многомерный массив неизвестной размерности, то придётся использовать рекурсию:
    object[] arr = new object[]
    {
        "Hello World!",
        new object[]
        {
            "Hello_1",
            "Hello_2",
            new object[]
            {
                "Hello_11",
                "Hello_22"
            }
        }
    };
    foreach (object item in arr)
    {
        ItemAction(item, str => Console.WriteLine(str));
    }
    
    static void ItemAction(object item, Action<string> action)
    {
        if (item is string str)
        {
            action(str);
        }
        else if (item is object[] arr)
        {
            foreach (object subitem in arr)
            {
                ItemAction(subitem, action);
            }
        }
    }
    Ответ написан
    Комментировать
  • Что такое compile time?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Compile time - выбор в коде, во время компиляции (comple time). То есть, компилятор видит код и понимает, какой тип нужен при создании объекта.
    var figure = new Circle(); // тип известен заранее (при компиляции)
    Console.WriteLine(figure.CalcArea());


    В отличие от compile time. может быть run time - во время работы программы, тип объекта выбирается в зависимости от некоторых условий (ввод пользователя, ответ от сервера, настройки в конфиг-файле, изменилась фаза луны).
    Самый простой вариант:
    var type = Console.ReadLine();
    Figure figure = null;
    switch (type.ToLower())
    {
        case "square": figure = new Square(10); break;
        case "rectangle": figure = new Rect(10, 20); break;
        case "circle": figure = new Circle(10); break;
    }
    if (figure != null)
    {
        Console.WriteLine(figure.CalcArea());
    }
    else
    {
        Console.WriteLine("Неверный тип фигуры");
    }

    Выбор делается в рантайме между заранее известными типами - конкретные возможные типы указаны в коде (в компайлтайме).

    Реальный тип (имя класса) можно спросить у пользователя (или из настройки), и вызвать его активатором:
    var typeName = Console.ReadLine();
    var type = Type.GetType(typeName);
    if (type != null)
        var figure = (Figure)Activator.CreateInstance(type);
        Console.WriteLine(figure.CalcArea());
    }
    else
    {
        Console.WriteLine("Неверный тип");
    }

    Но тип должен быть полным, с пространством имён, то есть, не "Square", а "MyApp.Figures.Square". Таким образом можно создать объект типа, которого вообще не было во время компиляции - например, тип был объявлен в отдельной библиотеке и добавлен в программу после компиляции (так делаются плагины, например). Но для плагинов код будет посложнее.
    Ответ написан
    Комментировать
  • Как обратиться к ListBox из кода?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Вам не нужно делать кастомный контрол для листбокса, такое поведение делается по другому:
    1) создаётся класс Anime, в котором нужно указать необходимые свойства аниме - Название, рейтинг, автор, серия, ссылка на скачивание и пр.
    2) создаётся вьюмодель окна (или части окна),
    2.1) во вьюмодели нужно сделать коллекцию объектов аниме (можно и List, но лучше ObservableCollection)
    3) вьюмодель указывается в окно как датаконтекст (тут есть несколько вариантов, ниже покажу самый прсотой вариант)
    4) привязать коллекцию аниме к списку ListBox, и указать имя свойства для показа (тут, видимо - Название).
    5) всё, список готов - будет видно в окне. Осталось получить конкретный элемент и его свойства - объект класса Anime указано в свойстве DataContext элемента списка (ListBoxItem) и остаётся привести к нужному классу:
    var anime = (Anime)AnimeListBox.Items.First().DataContext;
    Console.WriteLine(anime.Name + " " + anime.Rating);

    class Anime
    {
        public string Name {get; set;}
        public double Rating {get; set;}
        public string Author {get; set;}
        public string Serie {get; set;}
        public string Url {get; set;}
    }
    class AnimeListViewModel
    {
        public ObservableCollection<Anime> AnimeList {get; set;} = new ObservableCollection<Anime>
        {
            new Anime { Name = "Наруто",  Rating = 5, Author = "Масаси Кисимото"},
            new Anime { Name = "Стальной алхимик",  Rating = 5, Author = "Хирому Аракава"},
            new Anime { Name = "X",  Rating = 5, Author = "CLAMP"},
        };
    }


    <Window ...
        xmlns:app="clr-namespace:Anime">
        <Window.DataContext>
            <app:AnimeListViewModel/>
        </Window.DataContext>
        <Grid>
            <ListBox ItemsSource="{Binding AnimeList}"
                     DisplayMemberPath="Name"/>
                     x:Name="AnimeListBox"/>
        </Grid>
    </Window>
    Ответ написан
    Комментировать
  • Как получить понимание разработки игры?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Рекомендую пройти несколько (минимум два-три) туториала создания простой игры, где показывают большинство хотя бы основных частей - создание сцены, персонажи, перемещение, управление персонажем. Повторите всё так же, как у них рассказано, а не придумывать своё - для этого будет время, когда основные вопросы исчезнут.
    Ответ написан
    Комментировать
  • Какие есть способы у ViewModel привязать Model ко View?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Модель описывает данные. Представление определяет внешний вид экрана. Вьюмодель - это модель, которая предназначена для использования данных из модели внутри представления, причём во вьюмодели можно добавлять какие-то поля только для удобства представления. Одна вьюмодель вполне может использовать несколько разных моделей.

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

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

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

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string MiddleName { get; set; }
    }
    public class EditPersonVm : BaseViewModel // который реализует INotifyPropertyChanged
    {
        private readonly Person _person;
        public Person Person => _person;
    
        private string _firstName;
        public string FirstName
        {
            get => _firstName;
            set { _firstName = value; RaisePropertyChanged(); }
        }
        private string _lastName;
        public string LastName 
        {
            get => _lastName;
            set { _lastName= value; RaisePropertyChanged(); }
        }
        private string _middleName;
        public string MiddleName 
        {
            get => _middleName;
            set { _middleName= value; RaisePropertyChanged(); }
        }
    
        public string InitFullName => $"{_person.LastName} {_person.FirstName[0]}. {_person.MiddleName[0]}.";
    
        public RelayCommand OkButton { get; set; } = new RelayCommand(Save);
    
        public EditPersonVm(Person person)
        {
            _person = person;
            FirstName = person.FirstName;
            LastName = person.LastName;
            MiddleName = person.MiddleName;
        }
    
        private void Save()
        {
            //
        }
    }

    <Window ...>
        <StackPanel Orientation="Vertical">
            <Label Content="Изначальное имя:"/>
            <StackPanel>
                <TextBlock Text="{Binding Person.LastName}"/>
                <TextBlock> </TextBlock>
                <TextBlock Text="{Binding Person.FirstName}"/>
                <TextBlock> </TextBlock>
                <TextBlock Text="{Binding Person.MiddleName}"/>
            </StackPanel>
            <Label Content="Фамилия:"/>
            <TextBox Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}"/>
            <Label Caption="Имя:"/>
            <TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}"/>
            <Label Caption="Отчество:"/>
            <TextBox Text="{Binding MiddleName, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Content="OK"  Command="{Binding OkCommand}">
        </StackPanel>
    </Window>
    Ответ написан
    Комментировать
  • Как можно подправить код для выполнения задачи?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Лучше всего сделать 6 циклов от 0 до 9 и их счётчики использовать для получения суммы частей. Потому что получение цифр из числа - не такая простая операция, как кажется.

    int cnt1 = 0, cnt2 = 0, cnt3 = 0;
    var t0 = DateTime.Now;
    for (int i = 0; i <= 999999; i++)
    {
        var a1 = i / 100000 % 10;
        var a2 = i / 10000 % 10;
        var a3 = i / 1000 % 10;
        var b1 = i / 100 % 10;
        var b2 = i / 10 % 10;
        var b3 = i / 1 % 10;
        if (a1 + a2 + a3 == b1 + b2 + b3) cnt1++;
    }
    var t1 = DateTime.Now;
    
    for (int a1 = 0; a1 <= 9; a1++)
    for (int a2 = 0; a2 <= 9; a2++)
    for (int a3 = 0; a3 <= 9; a3++)
    for (int b1 = 0; b1 <= 9; b1++)
    for (int b2 = 0; b2 <= 9; b2++)
    for (int b3 = 0; b3 <= 9; b3++)
    {
        if (a1 + a2 + a3 == b1 + b2 + b3) cnt2++;
    }
    var t2 = DateTime.Now;
    
    for (int i = 0; i <= 999999; i++)
    {
        string kstr = i.ToString("D6");
        var a1 = int.Parse(kstr[0] + "");
        var a2 = int.Parse(kstr[1] + "");
        var a3 = int.Parse(kstr[2] + "");
        var b1 = int.Parse(kstr[3] + "");
        var b2 = int.Parse(kstr[4] + "");
        var b3 = int.Parse(kstr[5] + "");
        if (a1 + a2 + a3 == b1 + b2 + b3) cnt3++;
    }
    var t3 = DateTime.Now;
    
    Console.WriteLine($"{cnt1} {(t1 - t0).TotalMilliseconds} мс"); // 55252 21,0197 мс
    Console.WriteLine($"{cnt2} {(t2 - t1).TotalMilliseconds} мс"); // 55252 1,8051 мс
    Console.WriteLine($"{cnt3} {(t3 - t2).TotalMilliseconds} мс"); // 55252 106,1106 мс
    Ответ написан
    Комментировать
  • Unity 2d функция Collider 2D почему не работает?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    >У героя Body Type - Kinematic
    У героя Body Type должен быть Dynamic, иначе физика не заработает. Тип Kinematic - положение объектом полностью управляется скриптом, а не физикой. Значит, если вы хотите использовать физику, то используйте тип Dynamic.

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

    Для начала рекомендую серию из трёх статей (во второй см. комментарий для доработки старого кода в новых версиях юнити)
    Ответ написан
    3 комментария
  • Как сделать эффект бесконечного холста?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Можно представить, что у гуглокарт - бесконечный холст. Но это не гигантская картинка, это куча маленьких картинок. Просто потом нужно дорисовать нужные кусочки после скролла.
    1. Делаем двумерный массив картинок (или список списков картинок, чтобы количество можно было легко изменять). Размер картинок может быть любым, как больше размера канваса, так и меньше. Например, размер одной картинки 100x100
    2. Помещаем картинки на холст. Например, размер холста 500x500, то помещаем 25 картинок из массива. Возможно, дополнить по краям ещё по одной картинке, то есть, сделать размер холста 600x600 (или даже 700x700), но показывать только центр
    3. Отключить стандартные скроллбары холста, а скролл сделать самому - перетаскиванием мышки (можно - правой кнопкой, или настроить и скролл мышки). Потому что скроллбар показывает, где сейчас находится окно просмотра в общем документе, а в бесконечном холсте это не имеет смысла
    4. При прокрутке холста проверять, дошли ли до части, где нет картинки, тогда убрать дальний ряд или столбец и дорисовать новый ряд или столбец из массива.
    5. При дорисовке картинок из массива, сменить скролл холста - вернуть холст в центр, как раз там будут нужные картинки после удаления ряда картинок
    Ответ написан
    Комментировать
  • C#. Как посчитать количество слова из словаря в тексте?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Скорее всего, str.Contains(line1) показывает наличие (одно или несколько слов) в строке, а не количество слова в этой строке. Нужно искать слово до конца, даже после первого совпадения.

    int Count_Good = 0, Count_Bad = 0;
    
    // получаем словари в память, чтобы не открывать файлы словарей повторно
    // а также переводим слова в строчные
    var goods = File.ReadAllLines("slovar_good.txt").Select(word => word.ToLower());
    var bads = File.ReadAllLines("slovar_bad.txt").Select(word => word.ToLower());
    
    var file = File.OpenText(@"C:\Users\pavlovaa\Desktop\hik.txt");
    string str;
    while ((str = file.ReadLine()) != null)
    {
        str = str.ToLower();
        Count_Good += CountWords(str, goods);
        Count_Bad += CountWords(str, bads);
    }
    
    int CountWords(string str, IEnumerable<string> words)
    {
        if (string.IsNullOrWhiteSpace(str)) return 0;
        var count = 0;
        foreach (var word in words)
        {
            if (string.IsNullOrWhiteSpace(word)) continue;
            int pos;
            while ((pos = str.IndexOf(word)) != -1)
            {
                count++;
                str = str.Substring(pos + word.Length);
            }
        }
        return count;
    }
    Ответ написан
  • Как сделать вывод key и value из словаря в label?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Не используйте много Label-ов, поместите в список ListBox, и будет вам Щастье. Внешний вид можно настроить - фон BackColor=Control, границы BorderStyle=None. При добавлении записи в список можно преобразовать в строку как надо.
    Ответ написан
    2 комментария
  • Как выполнить команду по событию KeyDown для TextBox?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Можно использовать конвертер i:InvokeCommandAction из System.Windows.Interactivity.

    1) Добавить сборку System.Windows.Interactivity с помощью добавления ссылки (она в списке расширений).
    5cf0c5efb3bee685207836.png
    2) Добавить указанную сборку в xaml:
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

    3) Использовать i:EventTrigger и i:InvokeCommandAction:
    <TextBox>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="KeyDown">
                <i:InvokeCommandAction Command="{Binding KeyDownCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>


    Но если вы хотите там вызывать только обработку представления, например, запретить определённые клавиши (то есть, не нужны данные из слоя бизнес-логики), то есть смысл обработку сделать обычным способом - в code-behind (xaml.cs).
    Ответ написан
    Комментировать
  • Как заменить элементы в строке C#?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Нужно найти все места, где есть нужное слово.
    string s = "<div>Здесь нам нужно заменить некоторый текст, текст, текст</div>";
    var word = "текст";
    var replaceTo = "ТЕКСТ";
    var words = new List<int>();
    int lastPos = 0;
    int pos = 0;
    do
    {
        pos = s.IndexOf(word, lastPos);
        if (pos >= 0)
        {
            words.Add(pos);
            lastPos = pos + word.Length;
        }
    }
    while (pos >= 0);

    А теперь уже можно сделать с ними всё что угодно - заменять хоть все, хоть по одному.
    Например, замена первого слова
    var result = s.Substring(0, words[0]) + replaceTo + s.Substring(words[0] + word.Length);


    UPD. Можно вместо цикла найти слова с помощью регулярного выражения:
    string s = "<div>Здесь нам нужно заменить некоторый текст, текст, текст</div>";
    var word = "текст";
    var replaceTo = "ТЕКСТ";
    var words = new Regex(word).Matches(s).OfType<Match>().Select(match => match.Index).ToList();
    var result = s.Substring(0, words[0]) + replaceTo + s.Substring(words[0] + word.Length);

    Регекс может работать дольше, чем цикл. Зато код поиска в одну строку!
    Если нужны только слова целиком (не нужно искать "текстовый", например), то алгоритм поиска в цикле усложняется (проверять символ перед и после найденного текста, является ли пробел или пунктуация или ещё что-либо). А в регексе можно написать new Regex(@"\b" + word + @"\b").Matches(s), и регулярное выражение всё сделает само.
    Ответ написан
    3 комментария
  • Как сделать,чтобы из textbox можно было стирать введенный текст?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    if (e.KeyChar != (char)Keys.Back && !Regex.Match(Symbol, @"[а-яА-Я]|[a-zA-Z]").Success)

    См. https://toster.ru/q/635163, ответ и комментарии
    Ответ написан
  • C# Индексатор массива объектов - почему так (см.)?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Т.е. data[index] - это у нас объект класса Person

    Не совсем так, это не объект класса Person, а элемент с индексом index в массиве data.
    Индексатор, как и обычные свойства, - это просто сахарный синтаксис для создания методов геттера и сеттера (например, поля).

    Обычное свойство заменяется на методы GetProperty() и SetProperty(<type> value).
    А индексатор заменяется на методы GetIndexator(int index) и SetIndexator(int index, <type> value).

    В этих методах можно писать что угодно, в вашем первом варианте вы пишете код доступа к массиву data. Но тип аргументов (index и value) определяются типом индексатора, то есть, вы можете сделать вот так:
    Person[] data;
    
    public string this[int index]
    {
        get
        {
            return data[index]?.Name;
        }
        set
        {
            if (data[index] == null) data[index] = new Person();
            data[index].Name = value;
        }
    }

    Или даже вот так:
    Person[] data;
    
    public Person this[string name]
    {
        get
        {
            return data.FirstOrDefault(p => p.Name == name);
        }
        set
        {
            for (var index = 0; index < data.Length; index++)
            {
                if (person.Name == name)
                {
                    data[index] = value;
                    break;
                }
            }
        }
    }


    То есть, вы не можете сделать так, как вы написали:
    set
    {
         data[index].Name = value;
    }

    потому что типом value будет Person, а не string. (вряд ли, что у вас у свойства Name тоже тип Person, или у вас есть implicit приведение из Person в string).

    А что если там не один только Name будет, ещё поля ??..

    А если у вас там больше свойств, то будьте добры указать это в коде. Точно так же, как и если только один Name, в общем-то.
    Ответ написан
    2 комментария
  • Как в этом коде textbox'а сделать,чтобы можно было нажимать клавишу backspace(стереть)?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Использовать KeyDown вместо KeyPress. В KeyDown приходит не печатаемый символ, а код клавиши, в том числе, и управляющие клавиши.

    В вашем случае нужно вызывать оба события. Вначале вызывается KeyDown, и если он не выполнил работу обработки (не указан e.Handled = true), то вызовется обработчик KeyPress.

    Вот примерный код обработки backspace:
    private void textBox1_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Back)
        {
            var selectionStart = textBox1.SelectionStart;
            if (textBox1.SelectionLength > 0)
            {
                textBox1.Text = textBox1.Text.Substring(0, selectionStart) + textBox1.Text.Substring(selectionStart + textBox1.SelectionLength);
                textBox1.SelectionStart = selectionStart;
            }
            else if (selectionStart > 0)
            {
                textBox1.Text = textBox1.Text.Substring(0, selectionStart - 1) + textBox1.Text.Substring(selectionStart);
                textBox1.SelectionStart = selectionStart - 1;
            }
            
            e.Handled = true;
        }
    }
    Ответ написан