在我的Windows Phone 8应用程序中,我有地图控件,其中包含一组平面(图钉)。
Map在xaml中的定义如下:
<maps:Map x:Name="Map">
<maptk:MapExtensions.Children>
<maptk:MapItemsControl Name="Planes" ItemTemplate="{StaticResource PlaneTemplate}"/>
</maptk:MapExtensions.Children>
</maps:Map>
Planes MapItemsControl绑定到视图模型中对象的集合(ObservableCollection),如下所示:
private ObservableCollection<IPlane> _planes;
public ObservableCollection<IPlane> Planes
{
get { return _planes; }
set
{
_planes = value;
NotifyOfPropertyChange(() => Planes);
}
}
public async void GetPlanes()
{
IReadOnlyCollection<IPlane> planes = await realtimePlanes.GetAllPlanesAsync();
foreach (IPlane newPlane in planes)
{
this.Vehicles.Add(newPlane);
}
}
当我调用GetPlaes()时,要从web api获取飞机并更新集合,整个应用程序将释放几秒钟。通常情况下,Planes collectoin中会有150到300件物品。
我应该如何改进这一点以改善用户体验,以便在更新Planes系列时消除延迟?
我可以以某种方式强制在后台线程(?)中进行此集合更新,还是问题映射控制性能?它不能一次性快速地绘制所有项目吗?
以下是一些代码,我的实际应用程序的工作原理。
只需构建快速而肮脏的测试类/服务来生成测试平面:
public class TestRealtimePlaneService : IRealtimePlaneService
{
private Random random;
public async Task<IEnumerable<IRealtimePlane>> GetAllPlanesAsync()
{
this.random = new Random();
int count = 350;
double x0 = 24.9375;
double y0 = 60.170833;
int radius = 8000;
List<TestRealtimePlane> planes = new List<TestRealtimePlane>();
for (int i = 0; i < count; i++)
{
planes.Add(new TestRealtimePlane() { Location = getLocation(x0, y0, radius), Bearing = 1 });
}
await Task.Delay(5000); // Just to simulate webservice call
return planes;
}
// Taken from http://gis.stackexchange.com/questions/25877/how-to-generate-random-locations-nearby-my-location
public GeoCoordinate getLocation(double x0, double y0, int radius)
{
double radiusInDegrees = radius / 111000f;
double u = this.random.NextDouble();
double v = this.random.NextDouble();
double w = radiusInDegrees * Math.Sqrt(u);
double t = 2 * Math.PI * v;
double x = w * Math.Cos(t);
double y = w * Math.Sin(t);
double new_x = x / Math.Cos(y0);
double foundLongitude = new_x + x0;
double foundLatitude = y + y0;
return new GeoCoordinate(foundLatitude, foundLongitude);
}
}
这是实际的地图组件
<maps:Map x:Name="Map">
<maptk:MapExtensions.Children>
<maptk:MapItemsControl Name="Planes">
<maptk:MapItemsControl.ItemTemplate>
<DataTemplate>
<maptk:Pushpin GeoCoordinate="{Binding Location}" PositionOrigin="0.5,0.5">
<maptk:Pushpin.Template>
<ControlTemplate TargetType="maptk:Pushpin">
<Grid Width="45" Height="45" Background="Transparent">
<Polygon Fill="Yellow" Points="22,0 34,13, 12,13" Width="45" Height="45" RenderTransformOrigin="0.5,0.5">
<Polygon.RenderTransform>
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="{Binding Bearing}"/>
</Polygon.RenderTransform>
</Polygon>
<Ellipse Fill="Yellow" Stroke="Black" HorizontalAlignment="Center" VerticalAlignment="Center" Width="15" Height="15" StrokeThickness="2" />
</Grid>
</ControlTemplate>
</maptk:Pushpin.Template>
</maptk:Pushpin>
</DataTemplate>
</maptk:MapItemsControl.ItemTemplate>
</maptk:MapItemsControl>
</maptk:MapExtensions.Children>
</maps:Map>
似乎图钉模板对性能影响很大。如果我从图钉中删除我自己的控件模板,这样可以获得更快的速度。
答案 0 :(得分:2)
当您使用WPToolkit的MapExtension显示图钉时,您正在寻找麻烦。您希望Binding
ItemsSource到ObservableCollection<T>
为您提供更简便的方式来更新您的地图以及更好的&amp;流畅的用户体验。
不,不!我引用(我自己):
WPToolkit的MapExtensions糟透了驴子的屁股。这是一种 组件,让你质疑为什么你开始编程 第一名。
您可以将ItemsSource
绑定到集合,但您无法在XAML中执行此操作。不,你必须自己挖掘ItemsSource并在代码隐藏中明确设置它。
var control = MapExtensions.GetChildren(MyMap).OfType<MapItemsControl>().FirstOrDefault();
没那么糟糕,但等等,还有更多!如果集合中有项目,则不能只用新项目替换它,否则最终会出现异常。不,您必须清除这些项目,然后逐个添加新项目。恩,那就对了。一个接一个!
var planes = (await PlaneRepository.GetAllPlanesAsync()).ToList();
if (Planes.Any())
Planes.Clear();
foreach (var plane in planes)
Planes.Add(plane);
更新集合后,UI将被阻止。没有Dispatcher
,BackgroundWorker
或Task<T>
可以解决这个问题。
如果有人有任何疑问,请务必指出我哪里出错了。我设置了一个public GitHub repo(主分支)来分叉。
为了使地图无阻塞,你必须做一些妥协并完全抛弃MapExtensions。当您创建新的MapOverlay
,将一个Pushpin
作为内容,并将其手动添加到地图时,UI仍会保持响应。
但也有一个问题。如果你一次添加很多MapOverlays,那么你仍然可以暂时冻结。如果您故意延迟添加每个项目(例如,75毫秒),那么您的地图上的图钉会逐一出现并且UI保持响应,这会产生很好的效果。
Task.Factory.StartNew(() =>
{
if (message.Delay != null)
{
foreach (var plane in message.Planes)
{
AddPins(new[] {plane});
Thread.Sleep(message.Delay.Value);
}
}
});
private void AddPins(IEnumerable<IPlane> planes)
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
foreach (var plane in planes)
{
var pushpin = new Pushpin
{
Style = Resources["PlaneStyle"] as Style
};
var pushpinOverlay = new MapOverlay
{
GeoCoordinate = plane.Location,
Content = pushpin
};
_pushpinLayer.Add(pushpinOverlay);
}
});
}
使用MapOverlays的响应式地图示例位于in the same GitHub repo,但位于 no-map-extensions 分支。
这就是改善Windows Phone 8地图控制性能的方法:)。
答案 1 :(得分:-2)
因为你正在等待主线程。您应该使用BackgroundWorker关闭并获取平面,或者只是异步执行。然后更新UI。回到主线程非常简单。
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
//Stuff to do in UI thread
});