如何使用 WPF 可视化一个简单的 2D 世界(地图和元素) [英] How to use WPF to visualize a simple 2D world (map and elements)

查看:44
本文介绍了如何使用 WPF 可视化一个简单的 2D 世界(地图和元素)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 WPF 还很陌生,正在寻找解决下面描述的问题的简单方法.我已经尽量缩短了.

I'm fairly new to WPF and looking for a simple solution to the problem described below. I've tried to make this as short as possible.

我试图想象一个由以下建模的世界":

I'm trying to visualize a "world" that is modeled by:

  • 地图图像,已知原点以米为单位(例如左上角为 14,27),分辨率为厘米/像素.地图每隔几秒钟就会不断增长.地图很小,因此不需要分页/平铺.
  • 现实世界的元素和兴趣点.每个元素在地图区域内都有一个以米为单位的二维位置.此外,每个元素都可能移动.

关于模型方面,我有一个保存地图和元素的 WorldState 类:

Regarding the model side, I have a WorldState class that keeps the map and elements:

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 继承)应该以不同的方式绘制.可能有多个地图画布,以及元素的子集.

Now regarding the visualization, I've chosen the Canvas class to draw the map and elements. Each type of element (inherits from IWorldElement) should be drawn differently. There might be more than one map canvas, with a subset of the elements.

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

在代码中,我需要设置地图图像文件改变时:

In code, I need to set the map image file when it changes:

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

为了绘制元素,我有一个将 WorldLocation 转换为 (Canvas.Left, Canvas.Top) 的方法:

To draw the elements, I have a method to convert WorldLocation to (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);
}

现在的问题是,我应该如何将世界模型和画布粘在一起?这可以概括为:

Now for the question, how should I glue the world model and the canvas together? This can be summarized by:

  1. MapChangedWorldToScreen 函数的放置位置.
  2. 当元素移动时,世界位置需要转换为屏幕坐标.
  3. 应以不同方式绘制每种类型的元素,例如带有文本的椭圆或实心矩形.
  1. Where to put the MapChanged and WorldToScreen functions.
  2. When an element moves the world location needs to be converted to screen coordinates.
  3. Each type of element should be drawn differently, for example ellipse with text or filled rectangle.

在使用 WPF 时,推荐的实现胶水"层的方法是什么?

What is the recommended way to implement the "glue" layer when using WPF?

推荐答案

DataBinding 是要走的路.在这里阅读,http://msdn.microsoft.com/en-us/magazine/dd419663.aspx.

DataBinding is the way to go. Read here, http://msdn.microsoft.com/en-us/magazine/dd419663.aspx.

一旦您设置了视图模型并设置了视图的数据上下文.

Once you've set up a viewmodel and set the datacontext of the view.

我建议将您的元素放在一个 observablecollection 中,并将其绑定到画布中的一个 itemscontrol.

I suggest putting your elements in an observablecollection and bind it to an itemscontrol in the canvas.

对于要定位的元素,您必须为使用画布的 ItemsControl 创建自定义 ItemTemplate,而不是使用默认的堆栈面板作为容器.

For the elements to be positioned, you must create a custom ItemTemplate for the ItemsControl which uses a canvas, rather than the default stackpanel as container.

然后,您可以继续为各种类型的元素创建数据模板,以获得特定的外观和感觉 pr 元素类型.

Then you can go ahead and create datatemplate for the various types of elements you have to get a specific look and feel pr element type.

这是一个粗略的解决方案,希望对您有所帮助.

This is a rough outline of a solution, hope this helps.

示例:

<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;
        }
    }
}

视图模型

  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)
    {
    }
}

这篇关于如何使用 WPF 可视化一个简单的 2D 世界(地图和元素)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆