WPF可视化一个简单的2D世界

问 题

    

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:

  • A map image, with a known origin in meters (e.g. top-left corner is 14,27) and resolution in cm/pixel. The map keeps growing every few seconds. Maps are small so no paging/tiling is required.
  • Real-world elements and points of interest. Each element has a 2D position in meters within the map area. Also, each element might move.

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

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

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. 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.

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

解决方案

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.

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

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.

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.

Example:

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

转载于:https://www.cnblogs.com/PowerOfHeart/p/7237931.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值