Как использовать WPF для визуализации простого 2D-мира (карта и элементы) - PullRequest
3 голосов
/ 17 января 2011

Я довольно новичок в WPF и ищу простое решение проблемы, описанной ниже. Я пытался сделать это как можно короче.

Я пытаюсь визуализировать "мир", который моделируется с помощью:

  • Изображение карты с известным происхождением в метрах (например, верхний левый угол 14,27) и разрешением в см / пиксель. Карта продолжает расти каждые несколько секунд. Карты маленькие, поэтому пейджинг / мозаика не требуются.
  • Реальные элементы и достопримечательности. Каждый элемент имеет 2D позицию в метрах в пределах области карты. Кроме того, каждый элемент может двигаться.

Что касается стороны модели, у меня есть класс WorldState, в котором хранятся карта и элементы:

interface IWorldState
{
    IEnumerable<IWorldElement> Elements { get; }
    IMapData CurrentMap { get; }
}

interface IWorldElement
{
    WorldLocation { get; }
    event EventHandler LocationChanged;
}

interface IMapData
{
    string FilePath { get; }
    WorldLocation TopLeft { get; }
    Size MapSize { get; }
}

Теперь, что касается визуализации, я выбрал класс Canvas для рисования карты и элементов. Каждый тип элемента (наследуется от IWorldElement) должен отображаться по-разному. Может быть более одного холста карты с подмножеством элементов.

<Canvas x:Name="mapCanvas">
    <Image x:Name="mapImage" />
</Canvas>

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

void MapChanged(IWorldState worldState)
{
    mapImage.Source = worldState.CurrentMap.FilePath;
}

Чтобы нарисовать элементы, у меня есть метод для преобразования WorldLocation в (Canvas.Left, Canvas.Top):

Point WorldToScreen(WorldLocation worldLocation, IWorldState worldState)
{
    var topLeft = worldState.CurrentMap.TopLeft;
    var size = worldState.CurrentMap.Size;
    var left = ((worldLocation.X - topLeft.X) / size.X) * mapImage.ActualWidth;
    var top = ((worldLocation.Y - topLeft.Y) / size.Y) * mapImage.ActualHeight;
    return new Point(left, top);
}

Теперь вопрос: как мне склеить модель мира и холст? Это можно суммировать как:

  1. Где разместить функции MapChanged и WorldToScreen .
  2. Когда элемент перемещается, местоположение в мире необходимо преобразовать в экранные координаты.
  3. Каждый тип элемента должен быть нарисован по-разному, например, эллипс с текстом или заполненный прямоугольник.

Каков рекомендуемый способ реализации «клеевого» слоя при использовании WPF?

1 Ответ

7 голосов
/ 17 января 2011

DataBinding - это путь. Читайте здесь, http://msdn.microsoft.com/en-us/magazine/dd419663.aspx.

После того, как вы настроили модель представления и задали текстовый вид данных представления.

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

Чтобы позиционировать элементы, необходимо создать собственный ItemTemplate для ItemsControl, который использует холст, а не стандартную панель стека в качестве контейнера.

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

Это грубая схема решения, надеюсь, это поможет.

Пример:

<Window x:Class="WorldCanvas.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WorldCanvas"
Title="WorldCanvas" Height="500" Width="500"
>
<Window.Resources>
    <DataTemplate DataType="{x:Type local:HouseVM}" >
        <Canvas>
            <Rectangle Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}" Width="13" Height="23" Fill="Brown" />
        </Canvas>
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:BallVM}">
        <Canvas>
            <Ellipse Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}" Width="13" Height="13" Fill="Blue" />
        </Canvas>
    </DataTemplate>
</Window.Resources>
<Grid>
    <Canvas x:Name="TheWorld" Background="DarkGreen">
    <Button Content="MoveFirst" Click="Button_Click" />
    <ItemsControl ItemsSource="{Binding Entities}">
        <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding X}" />
                <Setter Property="Canvas.Top" Value="{Binding Y}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>
</Canvas>

</Grid>

    public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        var worldViewModel = new WorldViewModel();
        DataContext = worldViewModel;
    }

    void Button_Click(object sender, RoutedEventArgs e)
    {
        var viewModel = DataContext as WorldViewModel;
        if(viewModel != null)
        {
            var entity = viewModel.Entities.First();
            entity.X +=10;
        }
    }
}

ViewModels

  public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propertyName)
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

public class WorldViewModel : ViewModelBase
{
    ObservableCollection<EntityVM> entities;

    public ObservableCollection<EntityVM> Entities {
        get { return entities; }
        set 
        { 
            entities = value;
            NotifyPropertyChanged("Entities"); 
        }
    }

    public WorldViewModel()
    {
        Entities = new ObservableCollection<EntityVM>();
        int y=0;
        for(int i=0; i<30; i++)
        {
            if(i %2 == 0)
            {
                Entities.Add(new BallVM(i*10, y+=20));
            }
            else
            {
                Entities.Add(new HouseVM(i*20, y+=20));
            }
        }
    }       
}   

public class EntityVM : ViewModelBase
{
    public EntityVM(double x, double y)
    {
        X = x;
        Y = y;
    }

    private double _x;
    public double X
    {
        get
        {
            return _x;
        }
        set
        {
            _x = value;
            NotifyPropertyChanged("X");
        }
    }

    private double _y;
    public double Y
    {
        get
        {
            return _y;
        }
        set
        {
            _y = value;
            NotifyPropertyChanged("Y");
        }
    }
}

public class BallVM : EntityVM
{
    public BallVM(double x, double y) : base(x, y)
    {
    }
}

public class HouseVM : EntityVM
{
    public HouseVM(double x, double y)  : base(x, y)
    {
    }
}
...