Координаты круга с сохранением в файл в wpf

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

 private void Save(object sender, RoutedEventArgs e)
        {
            StreamWriter writer = new StreamWriter(@"C:\soh.txt"); //указываем путь к файлу.
                                                                   double x = MainCanvas.GetLeft(target);
                                                                   double y = MainCanvas.GetTop(target);
           /* foreach (Ellipse rect in MainCanvas.Children.OfType<Ellipse>())
            {
                double x = Canvas.GetLeft(rect);
                double y = Canvas.GetTop(rect);
           }*/
                writer.WriteLine(x);
                writer.WriteLine(y);
                writer.Close();
            }

Это мои попытки, а это класс точки:

 Ellipse target;  // круг
            Canvas maincanvas; // на котором рисуется точка
            SineWaveProvider32 sineWaveProvider = new SineWaveProvider32();

            public Target(Point center, double radius, Canvas canvas) // конструктор класса точки 
            {
                target = new Ellipse();
                target.Width = radius * 2;
                target.Height = radius * 2;
                Canvas.SetLeft(target, center.X - radius);
                Canvas.SetTop(target, center.Y - radius);
                target.Fill = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));

                sineWaveProvider.SetWaveFormat(16000, 1); // 16kHz mono
                sineWaveProvider.Frequency = 500;
                sineWaveProvider.Amplitude = 0.1f;
                waveOut = new WaveOut();
                waveOut.Init(sineWaveProvider);
                waveOut.Play();
                maincanvas = canvas;
                canvas.Children.Add(target);
                Panel.SetZIndex(target, 100);
            }

Если раскоментить, то не сохраняется в файл, если так оставить, то пишет ошибку, что невозможно преобразовать в UI элемент :(


Ответы (1 шт):

Автор решения: aepot

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

Ниже я покажу пример, как это может работать.

Пример содержит:

  • INotifyPropertyChanged - реализация этого интерфейса позволяет уведомлять привязанные контролы об изменениях в свойствах с помощью метода OnPropertyChanged().
  • Binding - привязки данных в XAML.
  • IValueConverter - нужен когда свойство содержит один тип данных, а контролу нужен другой. В данном случае свойство будет типа bool, а контролу нужно Visibility.

Я сделал Canvas размером с окно, поместил в него Ellipse, установил DataContext (это важно) и реализовал INotifyPropertyChanged. Далее добавил в класс все необходимые свойства, а в XAML разметке просто к ним привязался.

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

Конвертер, используется в XAML разметке

public class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value is bool v && v ? Visibility.Visible : Visibility.Collapsed;

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => value is Visibility v && v == Visibility.Visible ? true : false;
}

Код приложения

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private double _circleX;
    private double _circleY;
    private double _circleRadius;
    private bool _circleVisibile;
    private string _status;

    public double CircleX
    {
        get => _circleX;
        set
        {
            _circleX = value;
            OnPropertyChanged();
        }
    }

    public double CircleY
    {
        get => _circleY;
        set
        {
            _circleY = value;
            OnPropertyChanged();
        }
    }

    public double CircleRadius
    {
        get => _circleRadius;
        set
        {
            _circleRadius = value;
            OnPropertyChanged();
        }
    }

    public bool CircleVisible
    {
        get => _circleVisibile;
        set
        {
            _circleVisibile = value;
            OnPropertyChanged();
        }
    }

    public string Status
    {
        get => _status;
        set
        {
            _status = value;
            OnPropertyChanged();
        }
    }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        CircleX = 100;
        CircleY = 100;
        CircleRadius = 50;
        CircleVisible = true;
    }

    const string _fileName = "file.txt";

    private void SaveButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            using (StreamWriter sw = new StreamWriter(_fileName))
            {
                sw.WriteLine(CircleX);
                sw.WriteLine(CircleY);
                sw.WriteLine(CircleRadius);
                sw.WriteLine(CircleVisible);
            }
            Status = "Файл успешно записан";
        }
        catch (Exception ex)
        {
            Status = "Ошибка записи в файл: " +ex.Message;
        }
    }

    private void LoadButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            using (StreamReader sr = new StreamReader(_fileName))
            {
                CircleX = double.Parse(sr.ReadLine());
                CircleY = double.Parse(sr.ReadLine());
                CircleRadius = double.Parse(sr.ReadLine());
                CircleVisible = bool.Parse(sr.ReadLine());
            }
            Status = "Файл успешно прочитан";
        }
        catch (Exception ex)
        {
            Status = "Ошибка чтения из файла: " + ex.Message;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Полная разметка

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Width="800" Height="500" Loaded="Window_Loaded">
    <Window.Resources>
        <local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <StackPanel.Resources>
                <Style TargetType="{x:Type TextBox}">
                    <Setter Property="Margin" Value="5"/>
                    <Setter Property="Width" Value="120"/>
                </Style>
                <Style TargetType="{x:Type TextBlock}">
                    <Setter Property="Margin" Value="5"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                </Style>
                <Style TargetType="{x:Type Button}">
                    <Setter Property="Margin" Value="5"/>
                    <Setter Property="Padding" Value="5,0"/>
                </Style>
            </StackPanel.Resources>
            <TextBlock Text="Visible:"/>
            <CheckBox Margin="5" VerticalAlignment="Center" IsChecked="{Binding CircleVisible}"/>
            <TextBlock Text="X:"/>
            <TextBox Text="{Binding CircleX, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBlock Text="Y:"/>
            <TextBox Text="{Binding CircleY, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBlock Text="Radius:"/>
            <TextBox Text="{Binding CircleRadius, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Content="Save" Click="SaveButton_Click"/>
            <Button Content="Load" Click="LoadButton_Click"/>
        </StackPanel>
        <Canvas Grid.Row="1">
            <Ellipse Width="{Binding CircleRadius}" Height="{Binding CircleRadius}" Canvas.Left="{Binding CircleX}" Canvas.Top="{Binding CircleY}" Visibility="{Binding CircleVisible, Converter={StaticResource BoolToVisibilityConverter}}" Stroke="Black" StrokeThickness="1"/>
        </Canvas>
        <StatusBar Grid.Row="2">
            <TextBlock Margin="5" Text="{Binding Status}"/>
        </StatusBar>
    </Grid>
</Window>

Содержимое сохраненного файла выглядит вот так

100
100
50
True

Интерфейс выглядит вот так

введите сюда описание изображения

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

→ Ссылка