wpf - 如何使用 WPF 可视化一个简单的二维世界( map 和元素)

标签 wpf architecture model visualization

我是 WPF 的新手,正在寻找一个简单的解决方案来解决下面描述的问题。我已尽量缩短。

我正在尝试想象一个由以下人员建模的“世界”:

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

关于模型方面,我有一个保存 map 和元素的 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 类来绘制 map 和元素。应以不同方式绘制每种类型的元素(继承自 IWorldElement)。可能有不止一个 map Canvas ,其中包含元素的子集。

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

在代码中,我需要在 map 图像文件发生变化时设置它:

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

现在的问题是,我应该如何将世界模型和 Canvas 粘合在一起?这可以总结为:

  1. 放置 MapChangedWorldToScreen 函数的位置。
  2. 当元素移动时,需要将世界位置转换为屏幕坐标。
  3. 每种类型的元素都应该以不同的方式绘制,例如带有文本的椭圆或填充的矩形。

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

最佳答案

DataBinding 是必经之路。 在这里阅读,http://msdn.microsoft.com/en-us/magazine/dd419663.aspx .

设置 View 模型并设置 View 的数据上下文后。

我建议将您的元素放在一个 observablecollection 中,并将其绑定(bind)到 Canvas 中的一个 itemscontrol。

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

然后您可以继续为各种类型的元素创建数据模板,以获得特定的外观 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;
        }
    }
}

View 模型

  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 - 如何使用 WPF 可视化一个简单的二维世界( map 和元素),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4713406/

相关文章:

c# - 将 ToggleButton 绑定(bind)到 boolean 值

oop - 设计用户访问/权限类

ruby - 显示用户购买时出现错误,显示他购买的产品时出现产品图片和标题错误

c# - WCF 客户端对象反序列化通知

wpf - 在 WPF DataGrid 中调整行标题宽度的抓手

azure - 消息队列触发器在可扩展性方面是否比 Http 触发器更好?

c# - 从代码在 VS 中创建 UML 类图

php - 路由模型绑定(bind)和软删除 - Laravel 4

java - Model 类和 DAO 类的区别

c# - 如何在屏幕外渲染图表(来自 wpf 工具包)控件?