• Как с dll запустить диалоговое окно с браузером и получить результат?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Слишком общий вопрос :-)

    Манипулирование живым браузером - плохая идея, это ненадежно и можно споткнуться на всяких антивирусах. На уровне приложения лучше использовать компонент WebBrowser (по сути - Internet Explorer) или альтернативу (это будет минимум мегабайт 20-40 к размеру программы).

    Давным-давно писал статейку по работе с ВКонтакте, не знаю насколько сейчас актуальна и работает ли:
    Разработка desktop-приложения для «ВКонтакте» на C#

    Есть также библиотека с открытым исходным кодом:
    https://github.com/alekseynemiro/nemiro.oauth.dll

    Попробуйте выдернуть клиент для ВКонтакте (включая зависимости) или внедрить целиком, если не устроит присутствие лишней библиотеки в проекте (даже двух, если используется Windows Forms) :-)

    Готовые формы для Windows Forms:
    https://github.com/alekseynemiro/Nemiro.OAuth.Logi...

    Статья по использованию библиотеки: Авторизация по протоколу OAuth в проектах .NET Fra...

    Демонстрация ASP.NET MVC: demo-oauth.nemiro.net

    Получение адреса для авторизации делается примерно так:

    string applicationId = "идентификатор вашего приложения";
    string scope = "status,email";
    string authorizeUrl = "https://oauth.vk.com/authorize";
    authorizeUrl += String.Format("?client_id={0}&response_type=code", applicationId);
    // authorizeUrl += String.Format("&state={0}", "все что вам нужно");
    authorizeUrl += String.Format("&scope={0}", scope);

    Адрес authorizeUrl можно открыть в WebBrowser: webBrowser1.Navigate(authorizeUrl). В обработчике события DocumentCompleted можно получать код авторизации или маркер доступа (смотря какой response_type используется).

    На примере обработчика по умолчанию из моей библиотеки:

    protected internal void DefaultCallback(object sender, WebBrowserCallbackEventArgs e)
    {
      // ожидаем, когда будет получен результат
      if (e.Url.Query.IndexOf("code=") != -1 || e.Url.Query.IndexOf("oauth_verifier=") != -1)
      {
        // результат получен, извлекаем код авторизации
        // из строка параметров запроса
        // e.Url.Query // <= строка параметров запроса
        // либо oauth_verifier, либо code - точно уже не помню
      }
    }

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

    Если response_type=code, как в указанном выше коде получения адрес авторизации, то конвертировать код авторизации в маркер доступа можно выполнив запрос (скорее всего POST) к странице https://oauth.vk.com/access_token, на которую нужно передать следующие параметры:

    code=полученный код
    client_id=идентификатор приложения
    client_secret=секретный ключ
    grant_type=authorization_code

    Запрос маркера доступа нужно делать без WebBrowser, а например, с помощью WebClient или HttpClient.
    Если все правильно, сервер вернет access_token.
    Ответ написан
    Комментировать
  • Построение странного запроса в linq to db, как исправить?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    По умолчанию чувствительности к регистру быть не должно и если это не было сделано намерено, то нет необходимости делать ToUpper().
    return await dbSet.FirstOrDefaultAsync(u => u.Email == email);
    Ответ написан
  • Как запретить десериализацию объекта с Json.Net?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Попробуйте Required:

    [JsonProperty(PropertyName = "start", Required = Required.Always)]
    public long? start { get; set; }

    Либо проверяйте значение ключевого поля (или полей), если значение null, считайте что десериализация не дала результатов (outRequest = null).

    Ключевое поле - это поле, которое присутствует всегда и не может иметь значение null.

    Если зацепиться не за что, то можно и по всем пройтись:

    if (!outRequest.start.HasValue && !outRequest.finish.HasValue && !outRequest.template.HasValue)
    {
      outRequest = null;
    }

    По идее, если у ключевых полей использовать тип отличный от Nullable (например, long вместо long?), либо совместно с атрибутом Required.Always, то при десериализации должно выбрасываться исключение, если нужные поля в данных не будут найдены. Исключение можно перехватывать (try-catch) и таким образом понять, что входящие данные неверные.

    Еще можно сделать пользовательский конвертер - JsonConverter, но это может быть сложным решением, в плане «энергозатрат» :-)

    [JsonConverter(typeof(ORequestConverter))]
    public sealed class ORequest
    {
      [JsonProperty("start")]
      public long? start { get; set; }
    
      [JsonProperty("finish")]
      public long? finish { get; set; }
    
      [JsonProperty("template")]
      public int? template { get; set; }
    }
    
    public class ORequestConverter : JsonConverter
    {
    
      public override bool CanWrite
      {
        get
        {
          return false;
        }
      }
    
      public override object ReadJson
      (
        JsonReader reader, 
        Type objectType, 
        object existingValue, JsonSerializer serializer
      )
      {
        if (reader.TokenType == JsonToken.Null)
        {
          return null;
        }
    
        var target = (ORequest)Activator.CreateInstance(objectType);
    
        serializer.Populate(reader, target);
    
        if (!target.start.HasValue && !target.finish.HasValue && !target.template.HasValue)
        {
          // необходимые поля не найдены или имеют значение null
          // возвращаем null
          return null;
        }
    
        return target;
      }
    
      public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
      {
        throw new NotImplementedException();
      }
    
      public override bool CanConvert(Type objectType)
      {
        throw new NotImplementedException();
      }
    
    }
    Ответ написан
    Комментировать
  • Почему сохраняются старые значения в потоке?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Вы используете экземпляр BackgroundWorker, который живет на уровне класса.

    Всякий раз, когда вы нажимаете на кнопку, к экземпляру BackgroundWorker подключается новый обработчик события DoWork. Т.е. нажмете один раз на кнопку, будет один обработчик, нажмете два раза, то уже будет два обработчика, нажмете десять раз - будет десять обработчиков и все они будут работать. Это будет хорошо видно, когда обработчиков будет много, из-за рассинхронизации значение i будет меняться быстрее, хаотичней. Так что вывод переменной на уровень класса и присваивание ей нулевого значения - это не решение проблемы.

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

    private void button2_Click(object sender, EventArgs e) // запуск таймера класс BackgroundWorker
    {
      // создаем новый экземпляр BackgroundWorker
      bw = new BackgroundWorker();
      // остальной код
      key = true;
      // ...
    }

    Либо отключать обработчик, но для этого придется иметь ссылку на него (bw.DoWork -= ссылкаНаОбработчик). При таком варианте проще отказаться от анонимных функций и использовать обычные.

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

    EventHandlerList events = (EventHandlerList)typeof(Component).GetProperty
    (
      "Events", 
      BindingFlags.NonPublic | BindingFlags.Instance
    ).GetValue(bw, null);
    
    var k = typeof(BackgroundWorker).GetField
    (
      "doWorkKey", 
      BindingFlags.NonPublic | BindingFlags.Static
    ).GetValue(null);
    
    var handlers = events[k];
    
    Console.WriteLine
    (
      "Обработчиков {0}", 
      handlers.GetInvocationList().Length
    );
    Ответ написан
    1 комментарий
  • Nginx - как выключать и включать один из серверов в upstream?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Сделать два файла конфигурации и менять их местами, когда необходимо :-) Первый отключать, включать второй и наоборот.

    Или использовать директиву include.

    Процесс можно автоматизировать. В том числе и вариант с комментариями (хотя именно с комментариями будет сложно, можно более простой вариант найти). Думаю, можно просто сделать шаблон файла конфигурации с текстовым маркером вместо server или адреса сервера, заменять этот маркер на нужное значение (типа: sed -i.bak s/anymarker/192.168.12.2/g example.conf) и затем заменять файл конфигурации.
    Ответ написан
    4 комментария
  • Почему нет доступа к форме при использовании потока?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Как изменить textbox формы из другого класса?

    bw.DoWork += (o, eo) =>
    {
      for (;;)
      {
        i = time.Time(i);
        Thread.Sleep(1000);
        // https://msdn.microsoft.com/ru-ru/library/zyzhdc6b.aspx
        Invoke(new Action(() => {
          Text = "Таймер. Время: " + i;
        }));
      }
    };
    Ответ написан
    3 комментария
  • Как учитывать часовой пояс пользователя на asp.net сайте?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    DateTime.Now - просто сделать поиск и замену (по файлам). Можно ограничить поиск типами файлов (.cs, .cshtml, .aspx, .ascx), чтобы не зацепить лишнего.

    Что касается вывода времени в нужном часовом поясе, то возможно стоит это делать с помощью JavaScript. Для каждого отдельного пользователя выводить время со стороны сервера будет дорогим удовольствием (в плане затраты ресурсов).

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

    В представлениях делать вывод даты и времени можно в какие-нибудь span-ы, например:
    <p>
      <span class="datetime">
        @Html.GetDateTime(DateTime.Now)
      </span>
    </p>
    <p>
      <span class="datetime" data-timestamp="@Html.GetTimestamp()">
        @Html.GetDateTime(DateTime.Now)
      </span>
    </p>
    
    <p>Метод <b>Html.GetDateTime</b> вполне может возвращать дату в тегах, тогда будет:</p>
    <p>@Html.GetDateTime(DateTime.Now)</p>
    <p>а на выходе может быть:<p>
    <p>
      <span class="datetime" data-timestamp="123">
        15.11.2016 21:11:21
      </span>
    </p>
    <p>
      <b>timestamp</b> - позволит обойти проблему с форматированием, 
      при расчете времени на стороне клиента.
    </p>

    С UTC будет проще работать, но вполне можно конвертировать время из времени сервера, главное не затеряться во времени, особенно между зимой и летом :-)

    С представлениями придется повозиться. Хотя если у вас имена полей, содержащие дату и время, более ли менее унифицированы, то проблем особых быть не должно.

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

    В коде (C#) с заменой особых проблем быть не должно.

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

    Попробуйте для начала с представлениями (выводом) разобраться.
    А стоит проводить (в коде и базе) крупномасштабную замену времени сервера на UTC - время покажет.
    Ответ написан
    Комментировать
  • Как правильно отправлять soap-запросы, используя C# (в особенности - на сервера AdvCash)?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Обычно это так делается:

    cc28a9b90a31407aab7b6af750336a88.png

    Ссылка на службу: https://wallet.advcash.com:8443/wsm/merchantWebSer...

    3b825f13a4d64c25ae507b698550b686.png
    А дальше дело кода :-)

    // ServiceReference1 - см. на картинке выше, можно указать любое имя
    var client = new ServiceReference1.MerchantWebServiceClient();
    // и смотрите какие есть методы в client и что требуется для их использования
    // могут быть и другие подводные камни и особенности,
    // но свой код взаимодействия писать не придется
    // для большинства случаев достаточно будет автоматически созданных классов
    Ответ написан
    5 комментариев
  • Как оптимизировать запрос SELECT COUNT?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    SELECT COUNT(*) FROM - перебрать все строки.
    SELECT COUNT(id) FROM - перебрать все строки, в которых указанное поле (в данном случае id) имеет значение отличное от NULL.

    Без указания полей - наиболее оптимальный вариант для PostgreSQL:
    SELECT COUNT(*) FROM table WHERE field = value
    https://wiki.postgresql.org/wiki/Slow_Counting

    Если все совсем плохо, то как вариант, можно сделать собственный счетчик.

    Вот вырезка из PostgreSQL Wiki на русском языке:
    Почему "SELECT count(*) FROM bigtable;" работает медленно?
    Потому что не используется индекс. PostgreSQL выполняет проверку видимости каждой записи и таким образом производит последовательное сканирование всей таблицы. Если вы хотите, вы можете отслеживать количество строк в таблице с помощью триггеров, но это вызовет замедление при операциях записи в таблицу.
    Вы можете получить некоторую оценку. Колонка reltuples в таблице pg_class содержит информацию из результата выполнения последнего оператора ANALYZE на эту таблицу. На большой таблице, точность этого значения составляет тысячные доли процента, что вполне достаточно для многих целей.
    "Точный" результат count, часто не будет точным долгое время в любом случае; из-за конкурентности MVCC, count будет точным только на момент вызова запущенного запроса SELECT count(*) (или ограничиваться уровнями изоляции транзакций данной транзакции), и может потерять актуальность уже в момент завершения запроса. При постоянной работе транзакций, изменяющий таблицу, два вызова count(*), которые завершатся в одно и то же время могут показать разные значения, если изменяющая транзакция завершилась между их вызовами.
    https://wiki.postgresql.org/wiki/Часто_Задаваемые_...
    Ответ написан
    1 комментарий
  • Как изменить textbox формы из другого класса?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    private void Cl_SendMessage(string mess)
    {
        // если метод вызывается не из потока, к которому привязана форма
        // https://msdn.microsoft.com/ru-ru/library/system.windows.forms.control.invokerequired.aspx
        if (this.InvokeRequired)
        {
          // делаем вызов из потока формы
          // https://msdn.microsoft.com/ru-ru/library/zyzhdc6b.aspx
          this.Invoke(new Action<string>(this.Cl_SendMessage), mess);
          // уходим из этого метода
          return;
          // или можно в условии сделать else
          // кому как больше нравится
        }
        // else {
    
        // код находящийся здесь будет выполняться только если 
        // текущий поток - это поток в котором находится форма
        chatLogTB.Text += mess;
    
       // }
    }
    Ответ написан
    8 комментариев
  • C# как реализовать Контрол в WPF?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Чтобы написать элемент управления WPF в виде библиотеки на C# необходимо запустить Visual Studio (2015), выбрать меню Файл => Создать => Проект. В появившемся окне, перейти в раздел Шаблоны => Visual C# => Windows => Классическое приложение. В списке шаблонов найти и выбрать Библиотека пользовательских элементов управления WPF. Ввести название проекта и нажать на кнопку Ok. Написать какой-нибудь код :-)

    84553e4c4eea431dbeec25e86d0115fe.png
    Что касается написания кода, то возможно следующие ссылки вам помогут:
    Руководство по программированию в C#
    + Классы и структуры
    + Свойства

    Информацию по работе с Windows Presentation Foundation можно найти по следующей ссылке:
    https://msdn.microsoft.com/ru-ru/library/ms754130(...
    Ответ написан
    Комментировать
  • Как в MS Visual Studio сделать фрагмент кода (html) inline?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    В самой студии такого не припомню.

    Можно макрос написать, но лучше расширение поискать.

    Например, в CodeMaid есть функция Joining для склейки нескольких строк в одну.

    Или JoinLines - более легкий вариант.

    По горячим клавишам, если будут проблемы или просто захочется их переопределить, то сделать это можно в настройках Visual Studio: меню Сервис => Параметры => Окружение => Клавиатура.

    50ca11b86dad4ea7a2a7a096072e27bd.png
    Ответ написан
    Комментировать
  • Почему switch не работает?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    У вас двоеточие там, где должна быть точка с запятой:

    case valMetr == 0:
      valMetr = 0: // <<===== ошибка, должна быть точка с запятой
      break;
    case valMetr>1 && valMetr<=49:
      valMetr = 30: // <<===== ошибка, должна быть точка с запятой
      break;

    Вместо подобной конструкции switch лучше использовать if ... else.
    Быстрее будет работать, как минимум :-)
    Ответ написан
    1 комментарий
  • Как правильно сделать такой rewrite в nginx?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Скорее всего будет что-то типа такого:
    if ($request_uri ~ "^/robots.txt") {
    # или
    # if ($request_uri != "robots.txt") {
      rewrite ^(.*) http://www.site2.ru$1 permanent;
    }
    Ответ написан
    1 комментарий
  • Что такое web root на nginx?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Корневой каталог веб-приложения по умолчанию.

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

    Например, при обращении к серверу по IP-адресу должен открыться сайт по умолчанию, который располагается, в вашем случае, в папке /usr/share/nginx/html.

    При обращении к серверу Toster по IP тоже выдается страничка по умолчанию: 178.248.232.5
    e2b4eab4bc194e0790b984578eef98a1.png
    Ответ написан
    Комментировать
  • Какова правильная последовательность вызова методов Web Api?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Маркер доступа (access token) вдается один раз. На стороне клиента сохраняется в sessiongStorage либо в cookies. Используется при каждом запросе к API. Как правило, передавать маркер доступа лучше через заголовки. При использовании HTTPS заголовки будут зашифрованы.

    Для повышения безопасности, сервер может выдавать маркер доступа привязанный к конкретному IP (можно еще усложнить, например проверять браузер, тип системы и т.п.). Если адрес клиента не совпадает с адресом в базе, то аннулировать маркер доступа и предлагать клиенту получить новый маркер доступа.

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

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

    class ApiAccess : AuthorizeAttribute
    {
    
      public override void OnAuthorization(HttpActionContext actionContext)
      {
        if (actionContext == null)
        {
          throw new ArgumentNullException("actionContext");
        }
    
        if (!this.IsAuthorized(actionContext))
        {
          return;
        }
      }
    
      protected override bool IsAuthorized(HttpActionContext actionContext)
      {
        bool isAuthroized = base.IsAuthorized(actionContext);
    
        // логика проверки доступа
    
        IEnumerable<string> authItems;
        if (actionContext.Request.Headers.TryGetValues("Authorization", out authItems))
        {
          var auth = authItems.First().Split(' ');
          var token = service.GetToken(auth.Last());
          // ...
        }
    
        return isAuthroized;
      }
    }

    Фильтр добавляется к контроллерам WebAPI, где необходима проверка доступа:

    [ApiAccess]
    public class FileServerController : ApiController
    {
    
       // ...
    
    }

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

    let url = '/методAPI';
    let data = {}; // параметры запроса
    let headers = {
      'Authorization': 'ANYNAMEHERE ' + sessionStorage.getItem('token')
    };
    
    $.ajax({
      cache: false,
      processData: false,
      type: 'POST',
      url: url,
      contentType: 'application/json',
      dataType: 'json',
      data: JSON.stringify(data),
      headers: headers,
      success: (result) => {
        // успех
      },
      error: (x, textStatus, errorThrown) => {
         // ошибка
    
         // на сервер можно сделать исключение для плохих маркеров доступа
         // и проверить, если responseText содержит данный тип исключения,
         // то требовать у пользователя повторную авторизацию
        if (x.responseText) {
           let exception = JSON.parse(x.responseText);
           // AccessDeniedException - тип исключения в WebAPI, 
           // (скорее всего полное имя типа придется указывать)
           if (exception.ExceptionType == 'AccessDeniedException') { 
              // ...
           }
        }
      }
    });

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

    Если API используется через отдельный (независимый от API) сайт, который авторизует пользователей, то пользователя можно не привлекать к процедуре получения нового маркера доступа, сайт это может сделать сам и передать новый маркер своему пользователю.

    Если API используется в браузере, как есть, то маркер доступа можно передавать в параметрах запроса. Однако это небезопасно, т.к. данные будут в открытом виде.
    Ответ написан
    6 комментариев
  • Как вывести данные из бд изменив снова сохранить?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    По указанной вами ссылке ведь есть готовый код:
    var grid, dialog;
    grid = $('#grid').grid({
        // ваш url, который будет передавать данные из вашей бд
        dataSource: '/Grid/GetPlayers',
        uiLibrary: 'bootstrap',
        // имена полей, которые следует выводить в таблице
        columns: [
            { field: 'ID', width: 32 },
            { field: 'Name', sortable: true },
            { field: 'PlaceOfBirth', title: 'Place Of Birth', sortable: true }
        ],
        pager: { limit: 5, sizes: [2, 5, 10, 20] }
    });

    Со стороны сервера можно сделать Action в обычном контроллере, который будет делать запрос к базе и возвращать данные в формате JSON. Но лучше использовать WebAPI, это будет проще.

    Для удаления и сохранения отдельные методы.

    Набросал простой пример: https://github.com/alekseynemiro/examples/tree/mas...
    Возможно для проверки работы примера потребуется переустановить пакеты. Для этого откройте меню Сервис => Диспетчер пакетов Nuget => Консоль диспетчера пакетов и введите следующую команду:

    Update-Package –reinstall

    В контроллере есть метод GetAccounts, который возвращает записи из базы:

    [HttpPost]
    public JsonResult GetAccounts(int page, int limit)
    {
      using (var context = new Database1Entities())
      {
        // получаем записи для указанной страницы
        var result = context.Account.OrderBy(
           row => row.AccountID
        ).Skip((page - 1) * limit).Take(limit).ToArray();
        int total = context.Account.Count();
    
        // возвращаем json
        return Json(new { records = result, total = total });
      }
    }

    В представлении, в коде инициализации jQuery Grid Bootstrap, указывается ссылка на этот метод:

    grid = $('#grid').grid({
      // ссылка на действие GetAccounts в контроллере Home
      // запрос выполняется методом POST
      dataSource: { url: '/Home/GetAccounts', method: 'POST' },
      uiLibrary: 'bootstrap',
      columns: [
        { field: 'AccountID', sortable: true },
        { field: 'FirstName', sortable: true },
        { field: 'LastName', sortable: true },
        { field: 'Company', sortable: true },
        { field: 'Position', sortable: true }
      ],
      pager: { limit: 2, sizes: [2, 5, 10, 20] }
    });
    Ответ написан
    Комментировать
  • Как выделить строку таблицы при нажатии пр. клавиши мыши?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Добавить обработчик MouseDown:

    <DataGrid x:Name="dataGrid" MouseDown="dataGrid_MouseDown"/>

    Код обработчика примерно такой:

    private void dataGrid_MouseDown(object sender, MouseButtonEventArgs e)
    {
      if (e.RightButton == MouseButtonState.Pressed)
      {
        var row = DataGridRow.GetRowContainingElement(e.OriginalSource as FrameworkElement);
        if (row != null)
        {
          // dataGrid.SelectedIndex = row.GetIndex();
          dataGrid.SelectedItem = row;
        }
      }
    }
    Ответ написан
    Комментировать
  • Как сделать все ячейки равных размеров в tableLayoutPanel WinForms?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Указать размер в процентах, если размер должен изменяться в зависимости от размера формы.
    Либо указать точный размер в пикселях.

    Если требуются разные размеры для разных колонок/строк, то этого можно достичь с использованием вложенных TableLayoutPanel.

    Для выравнивания можно использовать колонки/строки с размером 100%. Примерно как в HTML.

    Вот пример формы для программного генерирования TableLayoutPanel с добавлением колонок и строк одинакового размера (этот код можно просто вставить в Form1):
    public partial class Form1 : Form
    {
    
      private Panel TableContainer = new Panel();
      private NumericUpDown Columns = new NumericUpDown();
      private NumericUpDown Rows = new NumericUpDown();
    
      public Form1()
      {
        InitializeComponent();
    
        this.Width = 420;
          
        var flowLayoutPanel = new FlowLayoutPanel();
        var LabelColumns = new Label();
        var LabelRows = new Label();
        var Create = new Button();
    
        // панель для редактора таблицы
        flowLayoutPanel.SuspendLayout();
        flowLayoutPanel.AutoSize = true;
        flowLayoutPanel.Controls.Add(LabelColumns);
        flowLayoutPanel.Controls.Add(Columns);
        flowLayoutPanel.Controls.Add(LabelRows);
        flowLayoutPanel.Controls.Add(Rows);
        flowLayoutPanel.Controls.Add(Create);
        flowLayoutPanel.Dock = DockStyle.Top;
        flowLayoutPanel.Location = new Point(0, 0);
    
        LabelColumns.AutoSize = true;
        LabelColumns.Dock = DockStyle.Fill;
        LabelColumns.ImageAlign = ContentAlignment.MiddleLeft;
        LabelColumns.Location = new Point(3, 0);
        LabelColumns.Size = new System.Drawing.Size(53, 29);
        LabelColumns.Text = "Колонок:";
        LabelColumns.TextAlign = ContentAlignment.MiddleLeft;
    
        LabelRows.AutoSize = true;
        LabelRows.Dock = DockStyle.Fill;
        LabelRows.Location = new Point(117, 0);
        LabelRows.Size = new Size(40, 29);
        LabelRows.Text = "Строк:";
        LabelRows.TextAlign = ContentAlignment.MiddleLeft;
    
        Columns.Dock = DockStyle.Fill;
        Columns.Location = new Point(62, 3);
        Columns.Minimum = 1;
        Columns.Size = new System.Drawing.Size(49, 20);
        Columns.Value = 3;
    
        Rows.Dock = DockStyle.Fill;
        Rows.Location = new Point(163, 3);
        Rows.Minimum = 1;
        Rows.Size = new Size(49, 20);
        Rows.Value = 2;
    
        Create.AutoSize = true;
        Create.Dock = DockStyle.Left;
        Create.Location = new Point(218, 3);
        Create.Size = new Size(75, 23);
        Create.Text = "Создать";
        Create.UseVisualStyleBackColor = true;
        Create.Click += new System.EventHandler(CreateTable);
    
        // контейнер для вывода готовой таблицы
        TableContainer.Dock = DockStyle.Fill;
          
        // добавляем необходимые элементы на форму
        this.Controls.Add(flowLayoutPanel);
        this.Controls.Add(TableContainer);
    
        this.Controls.SetChildIndex(flowLayoutPanel, 1);
        this.Controls.SetChildIndex(TableContainer, 0);
      }
    
      private void CreateTable(object sender, EventArgs e)
      {
        // удаляем предыдущую таблицу
        TableContainer.Controls.Clear();
    
        // создаем новую
        var tableLayoutPanel = new TableLayoutPanel();
        tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
        tableLayoutPanel.Location = new System.Drawing.Point(0, 0);
        tableLayoutPanel.Visible = true;
    
        tableLayoutPanel.ColumnCount = Convert.ToInt32(Columns.Value);
        tableLayoutPanel.RowCount = Convert.ToInt32(Rows.Value);
    
        // генератор случайных чисел для раскраски панелей (чтобы было видно)
        var rnd = new Random(DateTime.Now.Millisecond);
    
        // определяем размер одной колонки и строки, в процентах
        int width = 100 / tableLayoutPanel.ColumnCount;
        int height = 100 / tableLayoutPanel.RowCount;
    
        this.Text = String.Format("{0}x{1}", width, height);
    
        // добавляем колонки и строки
        for (int col = 0; col < tableLayoutPanel.ColumnCount; col++)
        {
          // добавляем колонку
          tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, width));
    
          for (int row = 0; row < tableLayoutPanel.RowCount; row++)
          {
            // добавляем строку
            if (col == 0) 
            {
              tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, height));
            }
    
            // добавляем цветную панель, чтобы было видно ячейку в таблице
            var panel = new Panel();
            panel.BackColor = Color.FromArgb(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));
            panel.Dock = DockStyle.Fill;
            tableLayoutPanel.Controls.Add(panel, col, row);
          }
        }
    
        // добавляем таблицу в контейнер
        TableContainer.Controls.Add(tableLayoutPanel);
      }
    
    
    }

    2ff063c8d06f474da07fa7df6c515be8.gif
    Ответ написан