Нарисовать Ellipse в Button

237
05 июля 2017, 23:35

Пишу простые крестики-нолики, чтобы протестить алгоритм Минимакс. Идея такая : в xaml создаю обычный Canvas размером примерно 3/4 окна, в нем рисую Button'ы 3 x 3, получается что-то вроде:

Дальше, я делаю так: при клике на кнопку, если номер клика чётный, рисую крестик, иначе кружок в середине кнопки. Вот код:

private void BoardButton_Click(object sender, RoutedEventArgs e)
{
    if (ClickCount > xDimension * yDimension) //3 * 3 = 9
        throw new Exception(); //Всего 9 клеток, если кликнули 9 раз - все клетки заполнены 
    Button _clicked = sender as Button;
    if (ClickCount % 2 == 0)
    {
        //Нарисовать крестик
    }
    else
    {
        Ellipse toDraw = CopyPlayer2Object; //Создает Ellipse со стандартными параметрами Width, Height, Stroke
        //Нужно нарисовать круг так, чтобы его центр был центром кнопки
        Canvas.SetLeft(toDraw, Canvas.GetLeft(_clicked) / 2);
        Canvas.SetTop(toDraw, Canvas.GetTop(_clicked) / 2); 
        CurrentLocation.Children.Add(toDraw); //Мой Canvas
    }
    ClickCount++;
}

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

P.S У меня очень мало опыта работы с WPF, по этому советы по тому, как сделать создание крестиков-ноликов удобнее, а сами крестики-нолики красивее приветствуются!

Answer 1

Для начала, сделаем наше поле квадратным. Простое решение — написать

Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"

(о более хорошем решении потом).

Теперь, нам нужно показывать ячейки одинакового размера, для этого подойдёт UniformGrid:

<UniformGrid Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
             Rows="3" Columns="3">
    ...
<UniformGrid/>

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

С ноликом просто:

<EllipseGeometry x:Key="NoughtGeo" RadiusX="1" RadiusY="1"/>

С крестиком немного сложнее, организуем «черепашью графику»:

<Geometry x:Key="CrossGeo">M 0,0 L 1,1 M 0,1 L 1,0</Geometry>

Теперь, рисуем наш Button. Сделаем отступ, растянем геометрию и сделаем линии потолще:

<Button Padding="15">
    <Path Data="{StaticResource NoughtGeo}" Stroke="DarkGray"
          Stretch="Uniform" StrokeThickness="3"/>
</Button>

Получаем примерно такой код:

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:SO4WPF"
        Title="Тест" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <Geometry x:Key="CrossGeo">M 0,0 L 1,1 M 0,1 L 1,0</Geometry>
            <EllipseGeometry x:Key="NoughtGeo" RadiusX="1" RadiusY="1"/>
        </Grid.Resources>
        <UniformGrid Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
                     Rows="3" Columns="3" Background="WhiteSmoke">
            <Button Padding="15" Margin="5">
                <Path Data="{StaticResource NoughtGeo}" Stroke="DarkGray"
                      Stretch="Uniform" StrokeThickness="3"/>
            </Button>
            <Button Padding="15" Margin="5">
                <Path Data="{StaticResource CrossGeo}" Stroke="DarkGray"
                      Stretch="Uniform" StrokeThickness="3"/>
            </Button>
        </UniformGrid>
    </Grid>
</Window>

и результат:

Сразу видим много повторяющегося кода, который нужно забросить в стиль. Поскольку у нас внутри контролы, положим их в ContentTemplate. Сам Content будет либо X, либо O.

<Style TargetType="Button">
    <Setter Property="Padding" Value="15"/>
    <Setter Property="Margin" Value="5"/>
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <Path Stroke="DarkGray" Stretch="Uniform" StrokeThickness="3" Name="P"/>
                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding}" Value="X">
                        <Setter TargetName="P" Property="Data"
                                Value="{StaticResource CrossGeo}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding}" Value="O">
                        <Setter TargetName="P" Property="Data"
                                Value="{StaticResource NoughtGeo}"/>
                    </DataTrigger>
                </DataTemplate.Triggers>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

Теперь кнопки оформляются просто:

<UniformGrid Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
             Rows="3" Columns="3" Background="WhiteSmoke">
    <Button>X</Button>
    <Button>O</Button>
    <Button> </Button>
    <Button> </Button>
    <Button>X</Button>
    <Button> </Button>
    <Button>X</Button>
    <Button> </Button>
    <Button>O</Button>
</UniformGrid>

Результат:

Теперь о проблеме с квадратным полем. Почему исходное решение не вполне хорошо? Посмотрим, как оно себя ведёт в случае изменения размеров окна:

В случае, когда ширина слишком маленькая, получается некрасиво.

Как с этим бороться? Без кода не получится, но можно воспользоваться вспомогатальным классом AspectRatioDecorator отсюда:

<local:AspectRatioDecorator>
    <UniformGrid Rows="3" Columns="3" Background="WhiteSmoke">
        <Button>X</Button>
        <Button>O</Button>
        <Button> </Button>
        <Button> </Button>
        <Button>X</Button>
        <Button> </Button>
        <Button>X</Button>
        <Button> </Button>
        <Button>O</Button>
    </UniformGrid>
</local:AspectRatioDecorator>

Получаем вот такой результат:

Мне не понравилось, что в последнем скриншоте при уменьшении поля крестики и нолики «схлопываются». Лучше, чтобы их граница уменьшалась пропорционально. Для этого немного поменяем нашу геометрию, включим в неё 15% отступа от краёв.

Для крестика это просто, добавим в начало «распорки»:

<Geometry x:Key="CrossGeo">
    M 0,0 M 1,1 M 0.15,0.15 L 0.85,0.85 M 0.15,0.85 L 0.85,0.15
</Geometry>

С ноликом придётся немного повозиться. Если просто уменьшить радиус, ничего не поменяется, ведь мы всё равно растягиваем его на всю ширину кнопки. Поэтому придётся положить отдельную невидимую распорку:

<GeometryGroup x:Key="NoughtGeo">
    <EllipseGeometry RadiusX="0.85" RadiusY="0.85"/>
    <Geometry>M -1,-1 M 1,1</Geometry>
</GeometryGroup>

Не забываем убрать Padding из стиля!

Получаем:

READ ALSO
Получить название сетевых подключений c#

Получить название сетевых подключений c#

Доброго времени суток, не подскажите, как получить имена (или пути) всех сетевых соединений ?

206
Телеграм прерывает соединение с ботами

Телеграм прерывает соединение с ботами

Здравствуйте! Написал 2х ботов, на С# (Robin Telegram API) и Python (Telebot)Проблема в том, что со временем (несколько дней) боты падают

204
Переменная let (область видимости) [дубликат]

Переменная let (область видимости) [дубликат]

На данный вопрос уже ответили:

321
Аналог MapArea в JavaScript, ссылка на изображении

Аналог MapArea в JavaScript, ссылка на изображении

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

291