zoukankan      html  css  js  c++  java
  • 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)
        {
        }
    }
    
  • 相关阅读:
    vue使用百度统计埋点
    修改JAVA字节码
    由前序遍历和中序遍历构建二叉树-Python
    二叉树的最大深度-Python
    二叉树的层序遍历-Python
    判断是否是对称二叉搜索树
    什么是主动学习方法
    验证二叉搜索树-Python
    DevExpress如何汉化XAF
    python install 失败?
  • 原文地址:https://www.cnblogs.com/PowerOfHeart/p/7237931.html
Copyright © 2011-2022 走看看