问题描述
在我的应用程序中,用户可以加载一个文本文件(分号分隔),该文件描述了要在 Bing 地图上变成 [Push] Pins 的位置特征,或者加载预先存在的地图数据(来自本地数据库)到同一端。
在从数据库加载的情况下,它工作得很好,地图上会为数据库中的每个相应记录显示一个 Pin 图,例如:
但是当我从文本文件的内容中填充数据库,然后根据这些值创建 Pin 图时,地图上只会显示 pin 的一个子集 - 实际上通常只是前两个!
我在从文本文件生成新记录后检查了数据库,所有记录确实都在那里(包括坐标(纬度和经度值)。两种情况下的代码似乎相同。但它是未显示所有 Pin 图...
Paris,France;;Paris;France;75000
Florence,Italy;;Florence;Italy;50100
Chelsea,London,England;23 Tedworth Square;Chelsea London;England;SW3 4DY
Kaltenleutgeben,Austria;;Kaltenleutgeben;Austria;2380
Vienna,Austria;;Vienna;Austria;1010
Weggis,Switzerland;;Weggis;Switzerland;6006
Heidelberg,Germany;;Heidelberg;Germany;69115
Munich,Germany;;Munich;Germany;80331
为了深入了解细节,以下是从文本文件加载地图数据的主窗体中的代码,例如上面显示的那个:
private void toolStripMenuItemCreateMapAndLocsFromFile_Click(object sender,EventArgs e)
{
bool pushpinsAdded = false;
string _currentMap = string.Empty;
RemoveCurrentpushpins();
using (var frmCre8MapAndLocsFromFile = new mdlDlgCreateMapAndLocsFromTSVFile())
{
if (frmCre8MapAndLocsFromFile.ShowDialog() == DialogResult.OK)
{
_currentMap = mdlDlgCreateMapAndLocsFromTSVFile.CurrentMap;
foreach (pushpin pin in frmCre8MapAndLocsFromFile.Pins)
{
this.userControl11.myMap.Children.Add(pin);
pushpinsAdded = true; // redundant after the first assignment
}
}
}
if (pushpinsAdded)
{
RightsizeZoomLevelForAllpushpins();
}
lblMapName.Text = currentMap;
lblMapName.Visible = true;
}
这是来自 mdlDlgCreateMapAndLocsFromTSVFile 的相关代码(是的,它的名字有误,因为我从 TSV 切换到 SCSV),上面调用了:
private void btnopenTSVFile_Click(object sender,EventArgs e)
{
string pathToFile = string.Empty;
string line = string.Empty;
int counter = 0;
openTSVFileDlg.Filter = "scsv files (*.scsv)|*.scsv|txt files (*.txt)|*.txt|All files (*.*)|*.*";
if (openTSVFileDlg.ShowDialog() == DialogResult.OK)
{
pathToFile = openTSVFileDlg.FileName;
fileNameOnly = Path.GetFileName(pathToFile);
fileNameOnly = Path.ChangeExtension(fileNameOnly,null);
CurrentMap = fileNameOnly;
if (SCSVFileAlreadyLoaded(fileNameOnly))
{
MessageBox.Show(String.Format("The file/table {0} has already been loaded/populated",fileNameOnly));
return;
}
StreamReader file = new StreamReader(pathToFile);
while ((line = file.ReadLine()) != null)
{
if (counter == 0)
{
InsertMapRecord(fileNameOnly);
}
counter++;
InsertMapDetailRecord(fileNameOnly,line);
}
}
}
InsertMapDetailRecord() 是将图钉添加到列表中的方法,它是:
private void InsertMapDetailRecord(string fileNameOnly,string line)
{
string keyRecordElements = string.Empty;
string[] lineElements;
lineElements = line.Split(';');
string locationName = lineElements[0].Trim();
string address = lineElements[1].Trim();
string city = lineElements[2].Trim();
string st8 = lineElements[3].Trim();
string zip = lineElements[4].Trim();
keyRecordElements = string.Format("{0} {1} {2} {3}",address,city,st8,zip).Trim();
if (MissingValues(lineElements))
{
MessageBox.Show("Could not insert record - one or more key values missing");
return;
}
if (InsertIntoCartographerDetail(fileNameOnly,locationName,zip))
{
Addpushpin(keyRecordElements,locationName);
}
else
{
MessageBox.Show("insert Failed");
}
}
Addpushpin() 是:
private async void Addpushpin(string _fullAddress,string _location)
{
iContentCounter = iContentCounter + 1;
// from https://stackoverflow.com/questions/65752688/how-can-i-retrieve-latitude-and-longitude-of-a-postal-address-using-bing-maps
var request = new GeocodeRequest();
request.BingMapsKey = "Gr8GooglyMoogly";
request.Query = _fullAddress;
var result = await request.Execute();
if (result.StatusCode == 200)
{
var toolkitLocation = (result?.ResourceSets?.FirstOrDefault())
?.Resources?.FirstOrDefault()
as BingMapsRESTToolkit.Location;
var latitude = toolkitLocation.Point.Coordinates[0];
var longitude = toolkitLocation.Point.Coordinates[1];
var mapLocation = new Microsoft.Maps.MapControl.WPF.Location(latitude,longitude);
this.Pin = new pushpin() { Location = mapLocation,ToolTip = _location,Content = iContentCounter
};
if (null == this.Pins)
{
this.Pins = new List<pushpin>();
}
this.Pins.Add(Pin); // specific to this form
UpdateDetailRecWithCoords(_location,latitude,longitude);
this.DialogResult = DialogResult.OK;
}
}
同样,就填充数据库而言,这完成了它应该做的事情,包括详细信息表的纬度和经度字段,但是当模式对话框打开时,在焦点传递回主窗体后,地图上只显示两个图钉关闭。
以下是调用 mdlDlgFrm_LoadExistingMap 的主窗体中的代码:
private void loadExistingMapToolStripMenuItem_Click(object sender,EventArgs e)
{
bool pushpinsAdded = false;
RemoveCurrentpushpins();
using (var frmloadExistingMap = new mdlDlgFrm_LoadExistingMap())
{
if (frmloadExistingMap.ShowDialog() == DialogResult.OK)
{
foreach (pushpin pin in frmloadExistingMap.Pins)
{
this.userControl11.myMap.Children.Add(pin);
pushpinsAdded = true;
}
}
}
if (pushpinsAdded)
{
RightsizeZoomLevelForAllpushpins();
}
lblMapName.Text = currentMap;
lblMapName.Visible = true;
}
这里是来自 mdlDlgFrm_LoadExistingMap 表单的代码,其中:
private void btnLoadSelectedMap_Click(object sender,EventArgs e)
{
string latitudeAsstr = string.Empty;
string longitudeAsstr = string.Empty;
List<MapDetails> lstMapDetails = new List<MapDetails>();
MapDetails md;
const int LOC_NAME = 0;
const int ADDRESS = 1;
const int CITY = 2;
const int ST8 = 3;
const int ZIP = 4;
const int NOTES = 5;
const int LATITUDE = 6;
const int LONGITUDE = 7;
mapName = cmbxMaps.Text.Trim();
if (mapName == string.Empty)
{
MessageBox.Show("Select a map from the combo Box");
cmbxMaps.Focus();
return;
}
try
{
Form1.currentMap = mapName;
string qry = "SELECT LocationName,Address1,City,StateOrSo,PostalCode," +
"MapDetailNotes,Latitude,Longitude " +
"FROM CartographerDetail " +
"WHERE FKMapName = @FKMapName";
var con = new sqliteConnection(connStr);
con.open();
sqliteCommand cmd = new sqliteCommand(qry,con);
cmd.Parameters.AddWithValue("@FKMapName",mapName);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
md = new MapDetails();
md.LocationName = reader.GetValue(LOC_NAME).ToString().Trim();
md.Address = reader.GetValue(ADDRESS).ToString().Trim();
md.City = reader.GetValue(CITY).ToString().Trim();
md.StateOrSo = reader.GetValue(ST8).ToString().Trim();
md.PostalCode = reader.GetValue(ZIP).ToString().Trim();
md.MapDetailNotes = reader.GetValue(NOTES).ToString();
latitudeAsstr = reader.GetValue(LATITUDE).ToString();
if (string.IsNullOrEmpty(latitudeAsstr))
{
md.Latitude = 0.0;
}
else
{
md.Latitude = Convert.Todouble(reader.GetValue(LATITUDE).ToString());
}
longitudeAsstr = reader.GetValue(LONGITUDE).ToString();
if (string.IsNullOrEmpty(longitudeAsstr))
{
md.Longitude = 0.0;
}
else
{
md.Longitude = Convert.Todouble(reader.GetValue(LONGITUDE).ToString());
}
lstMapDetails.Add(md);
}
AddpushpinsTolistofpushpins(lstMapDetails);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
this.Close();
}
// Addpushpins (plural)
private void AddpushpinsTolistofpushpins(List<MapDetails> lstMapDetails)
{
string fullAddress;
foreach (MapDetails _md in lstMapDetails)
{
fullAddress = string.Format("{0} {1} {2} {3}",_md.Address,_md.City,_md.StateOrSo,_md.PostalCode).Trim();
AddpushpinTolistofpushpins(_md.LocationName,fullAddress,_md.MapDetailNotes,_md.Latitude,_md.Longitude);
}
}
// Addpushpin (singular)
private async void AddpushpinTolistofpushpins(string location,string fullAddress,string mapDetailNotes,double latitude,double longitude)
{
iContentCounter = iContentCounter + 1;
string toolTip = string.Empty;
// if already have the record,including the coordinates,no need to make the REST call
if ((latitude != 0.0) && (longitude != 0.0))
{
if (mapDetailNotesExist(location))
{
toolTip = String.Format("{0}{1}{2}{1}{3},{4}{1}{5}",location,Environment.NewLine,longitude,mapDetailNotes.Trim());
}
else
{
toolTip = String.Format("{0}{1}{2}{1}{3},{4}",longitude);
}
var _mapLocation = new Microsoft.Maps.MapControl.WPF.Location(latitude,longitude);
this.Pins.Add(new pushpin()
{
Location = _mapLocation,ToolTip = toolTip,Content = iContentCounter
});
}
else
{
// from https://stackoverflow.com/questions/65752688/how-can-i-retrieve-latitude-and-longitude-of-a-postal- address-using-bing-maps
var request = new GeocodeRequest();
request.BingMapsKey = "Gr8GooglyMoogly";
request.Query = fullAddress;
var result = await request.Execute();
if (result.StatusCode == 200)
{
var toolkitLocation = (result?.ResourceSets?.FirstOrDefault())
?.Resources?.FirstOrDefault()
as BingMapsRESTToolkit.Location;
var _latitude = toolkitLocation.Point.Coordinates[0];
var _longitude = toolkitLocation.Point.Coordinates[1];
var mapLocation = new Microsoft.Maps.MapControl.WPF.Location(_latitude,_longitude);
this.Pins.Add(new pushpin()
{
Location = mapLocation,ToolTip = String.Format("{0}{1}{2}{1}{3},_latitude,_longitude,mapDetailNotes),Content = iContentCounter
});
UpdateLocationWithCoordinates(location,_longitude);
}
}
}
在我看来,无论是从文件创建地图还是从数据库加载地图,创建 Pin 图然后将它们显示在地图中的代码都是相同的。那么为什么行为不同?
更新
举一个更具体的例子,当通过包含如下内容的文件(前几行的摘录)创建地图时:
July 15 & 16,1895 - Music Hall,Cleveland,Ohio;; Cleveland;Ohio;44101
July 18,1895 - Soo Opera House or Hotel Iroquois,Sault Ste. Marie,Michigan;;Sault Ste. Marie;Michigan;49783
July 19,1895 - Casino Room,Grand Hotel,Mackinac,Michigan;;Mackinac;Michigan;49757
July 20,1895 - Grand Opera House,Petoskey,Michigan;;Petoskey;Michigan;49770
July 22,1895 - First Methodist Church,Duluth,Minnesota;;Duluth;Minnesota;55802
July 23,1895 - Hotel West & Reception and Supper,Minneapolis,Minnesota;;Minneapolis;Minnesota;55111
July 24,1895 - People's Church,St. Paul,Minnesota;;St. Paul;Minnesota;55101
July 27,1895 - Luncheon and Manitoba Club Supper,Winnipeg,Canada;;Winnipeg;Canada;R0G 0A1
...我一开始就明白了(只有一个子集,并且所有这些都具有相同的内容编号):
...但是当我此后立即通过“加载现有”加载地图时,我得到了一切:
?
更新 2
感谢 Reza Aghaei “The Bing Maps Whisperer”,它现在可以正常工作了:
解决方法
可能和可能的问题: 多线程和分配给实例级参考。
TLDR;在 AddPushpin() 中,使用局部变量而不是实例变量。
用 this.Pin = new Pushpin()
替换 var pin=new Pushpin()
,然后将此局部变量添加到集合中(您可能希望使用线程安全的集合更改此集合)
说明:
While Loop -> InsertMapDetailRecord() -> AddPushpin(){
1. thread is paused
2. New object is assigned to instance variable
3. Instance variable is added to collection
}
这可能是因为一个实例变量对象在被添加到集合之前被另一个实例覆盖。因此,集合中只有少数对象可用。 (正如您所提到的,子集已加载,但此计数不一致并且每次运行都会有所不同?)
,以下是我认为您需要应用的修复:
- 将
AddPushpin
签名更改为async Task AddPushpin(...)
- 然后在
InsertMapDetailRecord
中将调用更改为await AddPushpin(...);
- 将
InsertMapDetailRecord
签名更改为async Task InsertMapDetailRecord(...)
- 将
btnOpenTSVFile_Click
签名更改为async void btnOpenTSVFile_Click
- 然后在
btnOpenTSVFile_Click
中将调用InsertMapDetailRecord
更改为await InsertMapDetailRecord()
我也想你应该:
- 在
AddPushpin
方法中,删除this.DialogResult = DialogResult.OK;
- 在
btnOpenTSVFile_Click
中,在 while 循环结束后立即添加this.DialogResult = DialogResult.OK;
。
使用异步/等待
基本上,当您在方法 void M(...)
中想要调用返回 DoSomething(...)
的方法 Task
时,请将调用更改为 await DoSomething(...);
。然后如果方法M
是一个事件处理程序,则将签名更改为async void M(...)
,否则如果是普通方法,则将签名更改为async Task M(...)
。
设置对话结果
我认为您需要更改签名以及设置对话框结果的行的原因:在 AddPushpin
中,您将 DialogResult
设置为 OK,这意味着在添加第一个图钉后,包含对话框 (mdlDlgCreateMapAndLocsFromTSVFile
) 将关闭,您开始查看 Pins 属性(仍在幕后填充,然后在添加有机会添加到 Pins 属性的前几个图钉后,您处理mdlDlgCreateMapAndLocsFromTSVFile
并且不会再向地图添加图钉。
变量范围
我们没有看到完整的代码,但要注意变量/字段的范围。警告一下,在整个方法中访问类字段并不是一个好主意,它们不是局部变量,当您更改这些值时,其他方法也可以看到更改。