I am certain this question should bhave been asked before, but I am unable to find exactly what I am looking for;
Consider the following:
- Solution
-- Class Library Project [Caliburn.Micro] Referenced
--- [Models] Folder
---- LogEntryModel.cs
--- [ViewModels] Folder
---- LogEntryViewModel.cs
---- ShellViewModel.cs
-- WPF GUI Project [Caliburn.Micro] Referenced
--- [Views] Folder
---- LogEntryView.xaml
---- ShellView.xaml
so, I have 2 projects, one with Models and one with ViewModels and Views;
This is my Bootstrapper:
public class AppBootstrapper : BootstrapperBase
{
private CompositionContainer container;
public AppBootstrapper()
{
Initialize();
}
protected override void BuildUp(object instance)
{
this.container.SatisfyImportsOnce(instance);
}
/// <summary>
/// By default, we are configured to use MEF
/// </summary>
protected override void Configure()
{
var config = new TypeMappingConfiguration
{
DefaultSubNamespaceForViews = "WPFGUI.Views",
DefaultSubNamespaceForViewModels = "ClassLibrary.ViewModels"
};
ViewLocator.ConfigureTypeMappings(config);
ViewModelLocator.ConfigureTypeMappings(config);
var catalog =
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
this.container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(this.container);
batch.AddExportedValue(catalog);
this.container.Compose(batch);
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return this.container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
}
protected override object GetInstance(Type serviceType, string key)
{
var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = this.container.GetExportedValues<object>(contract);
if (exports.Any())
{
return exports.First();
}
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
var startupTasks =
GetAllInstances(typeof(StartupTask))
.Cast<ExportedDelegate>()
.Select(exportedDelegate => (StartupTask)exportedDelegate.CreateDelegate(typeof(StartupTask)));
startupTasks.Apply(s => s());
DisplayRootViewFor<IShell>();
}
}
Now, when I try to use LogEntryModel bound to a listbox, I reveice Cannot find view for ClassLibrary.Models.LogEntryModel.
I assume I need to 'tell' Caliburn to look for models in my Class Library project (how)
Should I reference Caliburn.Micro in my Class Library? (Since it's a GUI thing?)
Where should my ViewModels be, in the ClassLibrary or the GUI project?
[edit]
I changed my Folder structure, my VM's and Models are now grouped together,
I updated bootstrapper.cs:
var config = new TypeMappingConfiguration
{
DefaultSubNamespaceForViews = "WPFGUI.Views",
DefaultSubNamespaceForViewModels = "ClassLibrary.ViewModels"
};
ViewLocator.ConfigureTypeMappings(config);
ViewModelLocator.ConfigureTypeMappings(config);
The ShellViewModel still functions; but the LogEntryModel still displays:
Cannot find view for ClassLibrary.Models.LogEntryModel.
[edit 2]
LogEntryModel:
public class LogEntryModel
{
//GUID
public Guid GUID { get; set; }
//The log message string
public string Message { get; set; }
//The module that created the logentry (see enums Module for options)
public int Module { get; set; }
//The urgency (used for coloring: 0 = black (normal), 1 = red (error), 2 = cyan (info)
public int Severity { get; set; }
//User that triggered the logentry
public string UserID { get; set; }
//The datetime of the logentry
public DateTime LogEntryDateTime { get; set; }
}
LogEntryViewModel:
public class LogEntryViewModel
{
//This is for testing purposes only (I'd expect "Hello World" everywhere
public String Message { get; set; } = "Hello World";
}
LogEntryView.xaml:
<UserControl x:Class="ServicesUI_WPF.Views.LogEntryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFGUI.Views"
DataContext="ClassLibrary.ViewModels.LogEntryViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Red">
</Grid>
</UserControl>
if you desire to add new rules to link viewModel and view, you have to use ViewLocator:
some samples:
//link xxxxViewModel with xxxxViewX
ViewLocator.NameTransformer.AddRule(#"ViewModel", #"ViewX");
//case when view and viewmodel are not in same library
//link Cockpit.Core.Plugins.Plugins.Properties.xxxViewModel with
//Cockpit.General.Properties.Views.xxxView
ViewLocator.AddNamespaceMapping("Cockpit.Core.Plugins.Plugins.Properties", "Cockpit.General.Properties.Views");
I got it working; after reviewing some Youtube links I noticed a pattern in things I was missing;
LogEntryModel:
namespace ServicesTools.Models
{
public class LogEntryModel
{
//GUID
public Guid GUID { get; set; }
//The log message string
public string Message { get; set; }
//The module that created the logentry (see enums Module for options)
public int Module { get; set; }
//The urgency (used for coloring: 0 = black (normal), 1 = red (error), 2 = cyan (info)
public int Severity { get; set; }
//User that triggered the logentry
public string UserID { get; set; }
//The datetime of the logentry
public DateTime LogEntryDateTime { get; set; }
}
}
LogEntryViewModel
namespace ServicesTools.ViewModels
{
public class LogEntryViewModel : Screen
{
//Create a property that will keep all data from LogEntryModel in this BindableCollecton
private BindableCollection<LogEntryModel> _logEntries;
public BindableCollection<LogEntryModel> LogEntries
{
get { return _logEntries; }
set { _logEntries = value;
NotifyOfPropertyChange(() => LogEntries);
}
}
//On Instantiate; collect all the LogEntries from the datasource
public LogEntryViewModel()
{
LogEntries = new BindableCollection<LogEntryModel>(GlobalConfig.Connection.GetLogEntries());
}
}
}
I have no LogEntryView, because that's being called in DebugView.xaml:
<UserControl x:Class="ServicesTools.Views.DebugView"
xmlns:local="clr-namespace:ServicesTools.Views"
xmlns:vms="clr-namespace:ServicesTools.ViewModels;assembly=ServicesLibrary"
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls" xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:convert="clr-namespace:ServicesUI_WPF.Converters"
>
<Grid Grid.Row="2">
<Border Padding="5" BorderThickness="1" BorderBrush="{StaticResource CompanyCore1SolidBrush}">
<DockPanel>
<ScrollViewer CanContentScroll="True" VerticalScrollBarVisibility="Visible">
<ItemsControl ItemsSource="{Binding LogEntries}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Background="{Binding Severity, Converter={StaticResource SeverityToColorConverter}}" >
<TextBlock FontFamily="Consolas">
<TextBlock.Text>
<MultiBinding StringFormat="{}[{0:dd-MM-yy HH:mm:ss}] {1}({2}), {3}">
<Binding Path="LogEntryDateTime"/>
<Binding Path="Module" Converter="{StaticResource ModuleToEnumConverter}" />
<Binding Path="Module" />
<Binding Path="Message" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Border>
</Grid>
</Grid>
</UserControl>
I almost had it working in the ListBox, but I was missing "DisplayMemberPath" initialy;
What I was also missing in the LogEntryViewModel was actual code that created (and filled) the property BindableCollection<LogEntries>, there was actually nothing to bind to.
The final thing I had to edit was the DebugViewModel:
namespace ServicesTools.ViewModels
{
public class DebugViewModel : Screen
{
//Create an ObservableCollection property that will keep all LogEntries
private ObservableCollection<LogEntryModel> _logEntries;
public ObservableCollection<LogEntryModel> LogEntries
{
get { return _logEntries; }
set
{
_logEntries = value;
NotifyOfPropertyChange(() => LogEntries) ;
}
}
//On Instantiate; call GetLogEntries
public DebugViewModel()
{
GetLogEntries();
}
/// <summary>
/// Call a stored procedure to reset TrackAndTrace
/// Log an entry into the database with severity High on click
/// Log an entry into the database with severity Debug on finish
/// Refresh LogEntries
/// </summary>
public void TrackAndTraceReset()
{
//Log an Entry into the table
HelperFunctions.CreateLogEntry(logEntryMessage:$"User Clicked the Track & Trace reset button", Enums.Severity.High, Enums.Module.Debug);
//TODO something [...]
HelperFunctions.CreateLogEntry(logEntryMessage: $"And it Worked!", Enums.Severity.Debug, Enums.Module.Debug);
//Refresh the list of LogEntries
GetLogEntries();
}
/// <summary>
/// Clear the current property LogEntries (if not Null),
/// then instantiate a new LogEntryViewModel and insert LogEntryViewModel.LogEntries property into LogEntries
/// </summary>
/// <returns>ObservableCollection<LogEntryModel></returns>
private ObservableCollection<LogEntryModel> GetLogEntries() {
//If LogEntries is null, do nothing; otherwise Clear it
LogEntries?.Clear();
//Instantiate new LogEntryViewModel
LogEntryViewModel _lEVM = new LogEntryViewModel();
//Insert Property into LogEntries property
LogEntries = _lEVM.LogEntries;
//TODO: if filter exists, filter the list
return LogEntries;
}
}
}
I am still testing with if I actually need to instantiate a new LogEntryViewModel() each time the log entries get updated, but that was the easiest way to update all entries in the list.
Related
This question already has answers here:
DataBinding in WPF?
(1 answer)
Why does WPF support binding to properties of an object, but not fields?
(2 answers)
Closed 1 year ago.
Just when I think I've got a handle on WPF/XAML and view model binding I hit a snag. Here's a streamlined example of an issue I'm having:
Consider the following classes:
namespace TestApp
{
public class BandMember
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Band
{
public BandMember[] member = new BandMember[4];
public Band()
{
for (int i= 0; i < 4; i++)
{ member[i] = new BandMember(); }
}
}
public class Modify
{
public static Band SetBandMembers()
{
Band b = new();
b.member[0].FirstName = "John";
b.member[0].LastName = "Lennon";
b.member[1].FirstName = "Paul";
b.member[1].LastName = "McCartney";
b.member[2].FirstName = "Ringo";
b.member[2].LastName = "Starr";
b.member[3].FirstName = "George";
b.member[3].LastName = "Harrison";
return b;
}
}
}
In the View Model I have
namespace TestApp
{
class ViewModel
{
public Band Beatles { get; set; }
public string Test { get; set; }
public ViewModel()
{
Beatles = new();
Beatles = Modify.SetBandMembers();
Test = "This is the test";
}
}
}
And in the XAML:
<TextBlock Text="{Binding Beatles.member[3].LastName}"
Grid.Row="0"
Width="200" Height="60"/>
<TextBlock Text="{Binding Test}"
Grid.Row="0"
Width="200" Height="60"/>
I know the view model binding is correct, as the {Binding Test} text block displays correctly. But trying to bind to Beatles fails, the text block is blank.
I tried the exact same thing in a console app, with the same classes and calling the same code found in the view model constructor, and was able to write ln the Beatles members' values.
The error I get in VS says when I look at the debugging trace says:
Error: 40 : BindingExpression path error: 'member' property not found on 'object' ''Band' (HashCode=30265903)'.
BindingExpression:Path=Beatles.member[3].LastName;
DataItem='ViewModel'
I have a combobox, which draws it's items from an ObservableCollection of a custom type using Bindings. I've set the DisplayMemberPath so it displays the correct string and stuff. Now I'm fiddling with the SelectedItem/SelectedValue. It needs to be dependant on the selected item of a ListBox, which is bound to a different ObservableCollection of another custom type, but which has a property of the same type of the ComboBox list.
How can I bind this using MVVM? Is it even possible?
I've got my code here:
MainWindowViewModel.cs
private ObservableCollection<Plugin<IPlugin>> erpPlugins;
public ObservableCollection<Plugin<IPlugin>> ERPPlugins
{
get
{
return erpPlugins;
}
set
{
erpPlugins = value;
OnProprtyChanged();
}
}
private ObservableCollection<Plugin<IPlugin>> shopPlugins;
public ObservableCollection<Plugin<IPlugin>> ShopPlugins
{
get
{
return shopPlugins;
}
set
{
shopPlugins = value;
OnProprtyChanged();
}
}
private ObservableCollection<Connection> connections;
public ObservableCollection<Connection> Connections
{
get {
return connections;
}
set
{
connections = value;
}
}
public MainWindowViewModel()
{
instance = this;
ERPPlugins = new ObservableCollection<Plugin<IPlugin>>(GenericPluginLoader<IPlugin>.LoadPlugins("plugins").Where(x => x.PluginInstance.Info.Type == PluginType.ERP));
ShopPlugins = new ObservableCollection<Plugin<IPlugin>>(GenericPluginLoader<IPlugin>.LoadPlugins("plugins").Where(x => x.PluginInstance.Info.Type == PluginType.SHOP));
Connections = new ObservableCollection<Connection>
{
new Connection("test") { ERP = ERPPlugins[0].PluginInstance, Shop = ShopPlugins[0].PluginInstance } // Debug
};
}
Connection.cs
public class Connection
{
public string ConnectionName { get; set; }
public IPlugin ERP { get; set; }
public IPlugin Shop { get; set; }
public Connection(string connName)
{
ConnectionName = connName;
}
}
And the XAML snippet of my ComboBox:
<ComboBox
Margin="10,77,232,0"
VerticalAlignment="Top"
x:Name="cmbERP"
ItemsSource="{Binding ERPPlugins}"
SelectedItem="{Binding ElementName=lbVerbindungen, Path=SelectedItem.ERP}"
DisplayMemberPath="PluginInstance.Info.Name"
>
Alright, I solved it by changing the IPlugin type in the Connection to Plugin. Why I used IPlugin there in the first place is beyond my knowledge. But like this, I have the same type of Plugin everywhere.
Thanks for your help, appreciate it
Due to architecture design specifications, I have an application that fills its views from ClassLibraries. The application itself behaves like a sort of Integrator.
Now I need to add localization resources and I can successfully achieve it by adding *.resw files but only if the control is declared inside of the Application project.
What I actually need is to being able to share those resources across the ENTIRE SOLUTION somehow.
Then, the point is to being able to translate any control's content of the solution by using localization resources, preferably using the structure explained above.
For example, I have this following view, which fills the TextBlocks' content depending on the selected language:
<ComboBox x:Name="Languages"
ItemsSource="{Binding Languages}"
SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}">
<i:Interaction.Behaviors>
<iCore:EventTriggerBehavior EventName="SelectionChanged">
<iCore:InvokeCommandAction Command="{Binding ChangeLanguage}" />
</iCore:EventTriggerBehavior>
</i:Interaction.Behaviors>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LanguageName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="{Binding Model.HelloText}" FontSize="50" Foreground="Red"/>
<TextBlock Text="{Binding Model.HowAreYouText}" FontSize="50" Foreground="Red"/>
<BFview:BFView />
</StackPanel>
Where BFView is a view stored in another project (has two dummy textblocks also)
The Model of that view:
public class MainModel : TranslatableStrings
{
private string helloText, howareuText;
public string HelloText
{
get { return this.helloText; }
set { SetProperty(ref this.helloText, value); }
}
public string HowAreYouText
{
get { return this.howareuText; }
set { SetProperty(ref this.howareuText, value); }
}
}
And the base class of the Model is just a contractual class since it has no implementation, but a base type:
public abstract class TranslatableStrings : BindableBase { }
Then, the View data context is the following one:
public class MainViewModel : BaseViewModel
{
private ObservableCollection<MainViewListRscs> languages = new ObservableCollection<MainViewListRscs>();
private ICommand changeLang;
private MainModel model = new MainModel();
public MainViewModel()
{
Languages = new ObservableCollection<MainViewListRscs>()
{
new MainViewListRscs { LanguageCode = "es-ES", LanguageName = "Español" },
new MainViewListRscs { LanguageCode = "en-EN", LanguageName = "English" },
new MainViewListRscs { LanguageCode = "fr-FR", LanguageName = "Français" },
new MainViewListRscs { LanguageCode = "de-DE", LanguageName = "Deutsch" }
};
}
public ICommand ChangeLanguage
{
get { return changeLang = changeLang ?? new DelegateCommand(OnChangeLanguageRequested); }
}
public ObservableCollection<MainViewListRscs> Languages
{
get { return this.languages; }
set
{
this.languages = value;
OnPropertyChanged();
}
}
public MainViewListRscs SelectedLanguage { get; set; }
public MainModel Model
{
get { return this.model; }
set { this.model = value; }
}
private void OnChangeLanguageRequested()
{
Logger.Debug("MAINVIEW", SelectedLanguage.LanguageName + " selected.");
TranslateManager.UpdateStrings<TranslatableStrings>(SelectedLanguage.LanguageCode, this.Model);
}
public override Task OnNavigatedFrom(NavigationEventArgs args)
{
return null;
}
public override Task OnNavigatedTo(NavigationEventArgs args)
{
return null;
}
}
And the TranslateManager:
public class TranslateManager
{
public async static void UpdateStrings<T>(string langCode, T instance) where T : TranslatableStrings
{
//Get all the classes that implement TranslatableStrings
var currentAssembly = instance.GetType().GetTypeInfo().Assembly;
var translatableClasses = currentAssembly.DefinedTypes.Where(type => type.BaseType == typeof(T)).ToList();
//Open RESX file
ResourceLoader resx = ResourceLoader.GetForCurrentView(langCode);
foreach(var Class in translatableClasses)
{
foreach(var property in Class.DeclaredProperties)
{
string value = resx.GetString(property.Name);
var vmProp = instance.GetType().GetTypeInfo().GetDeclaredProperty(property.Name);
vmProp.SetValue(instance, value);
}
}
}
}
I have achieved changing the two TextBlocks of the MainView but not the view in another project. What I would need to do is to get a list of assemblies contained in a solution. I guess that getting just this would make everything work since I'm using a generic implementation.
Any suggestion will be much appreciated.
Thanks!
Your translation files are loaded as resources. So you can access them anywhere, even in other projects by doing something like
private ResourceLoader _resourceLoader = new ResourceLoader();
var someTranslation =_resourceLoader.GetString("your_localization_key");
Wrap this code nicely into a lib so that you can have an easy access to it from everywhere, and there you go !
I'm new to WPF and trying to wrap my head around the preferred way to handle data. I found this link that explains the databinding for a tree view. I have tried to create my code in a similar way, but I can't see why that code runs fine and mine doesn't.
Anyway, I've defined some class for artists/albums/songs
class LibArtist
{
public string Name { get { return mName; } }
string mName;
public ObservableCollection<LibAlbum> Albums;
public LibArtist(string name)
{
mName = name;
Albums = new ObservableCollection<LibAlbum>();
}
}
class LibAlbum
{
public string Name { get { return mName; } }
public string Artist { get { return mArtist.Name; } }
public uint Year { get { return mYear; } }
public ObservableCollection<LibSong> mSongs = new ObservableCollection<LibSong>();
uint mYear;
LibArtist mArtist;
string mName;
public LibAlbum(string pName, LibArtist pArtist, uint pYear)
{
mName = pName;
mArtist = pArtist;
mYear = pYear;
}
}
class LibSong
{
public string Title { get { return mName; } }
public string Artist { get { return mArtist; } }
public string Album { get { return mAlbum; } }
public string Location { get { return mLocation; } }
public uint Year { get { return mYear; } }
string mName;
uint mYear;
string mAlbum;
string mArtist;
string mLocation;
public LibSong(string pSongLocation)
{
mLocation = pSongLocation;
TagLib.File lFile = TagLib.File.Create(pSongLocation);
mAlbum = lFile.Tag.Album;
mName = lFile.Tag.Title;
mArtist = lFile.Tag.AlbumArtists.Length > 0 ? lFile.Tag.AlbumArtists[0] : "???";
//use tag lib to fill the data if this file exists
mYear = lFile.Tag.Year;
}
public override bool Equals(object obj)
{
LibSong temp = obj as LibSong;
if (temp == null)
return false;
if (temp.Location == this.Location)
return true;
if (temp.Artist == this.Artist && temp.Album == this.Album && temp.Year == this.Year)
return true;
return false;
}
}
And these sit in a library class:
class Library
{
public SortedDictionary<string, List<string>> mArtistsToAlbums;
SortedDictionary<string, List<LibSong>> mAlbumsToSongs;
public List<LibSong> mSongList;
public ObservableCollection<LibSong> mSongList2;
public ObservableCollection<LibAlbum> mAlbumList;
public ObservableCollection<LibArtist> mArtistList;
...
}
In my main window, I set the data context of my treeview to the library object:
public MainWindow()
{
mPlayer = new izPlayer(0);
InitializeComponent();
libraryTreeView.DataContext = mLibrary;
mLibrary = new Library();
mLibrary.CreateTestData();
In my xaml, I define the treeview like so:
<TreeView Name="libraryTreeView"
HorizontalAlignment="Left"
ItemsSource="{Binding mArtistList}"
Height="443" Margin="10,50,0,0" VerticalAlignment="Top" Width="344" MouseDoubleClick="libraryTreeView_MouseDoubleClick"
>
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And when I run this, I don't get anything displayed in the treeview. As I said, I'm not sure why this is different from the example code, or why it isn't displaying the data inside mArtistList.
Any help would be appreciated!
Specifically for the TreeView Dennis' answer is a great resource. If you're not getting any items even in at the top level thought, it may be due to invalid binding sources. It looks like Library is declaring public fields
public ObservableCollection<LibArtist> mArtistList;
In order to use binding in the XAML these sources need to be public properties
public ObservableCollection<LibArtist> mArtistList { get; set; }
This is totally different from example code (I mean XAML difference).
The main concept for the data-bound TreeView in WPF is that you must describe hierarchical data templates for your nodes, because you want to display hierarchical data.
Your XAML should look like this:
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type yourNamespace:LibArtist}" ItemsSource="{Binding Albums}">
<!-- the template tree for displaying artist's data -->
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type yourNamespace:LibAlbum}" ItemsSource="{Binding Songs}">
<!-- the template tree for displaying song's data -->
</HierarchicalDataTemplate>
<!-- and so on -->
</TreeView.Resources>
Im struggling with Observable collections and using it to add pushpins onto a silverlight bing map. Im trying to build up a collection here using Linq. But im getting the error under every "PushPinItems" instance in my code saying:
'observable_collection_test.Map.PushPinItems' is a 'field' but is used like a 'type' c:\users\dan\documents\visual studio 2010\Projects\observable collection test\observable collection test\Map.xaml.cs 26 38 observable collection test
Not sure whats going on here, am I declaring/constructing it wrong or something?
Im new to Observable collections (and most of c#!) so any help/advice welcome. Many thanks.
UPDATE:
This seems to be ok now, the above issue, but now its not binding my items to pushpins.
I have looked at the "PushPins = pushPinCollection;" method and all 143 items are in there with lat, long and location propertiess with the correct data- as per this breakpoint:
Maybe there is an issue with my XAML binding?
Here is the updated code:
namespace observable_collection_test
{
public partial class Map : PhoneApplicationPage
{
private ObservableCollection<SItem2> _PushPins;
public event PropertyChangedEventHandler PropertyChanged;
public Map()
{
InitializeComponent();
getItems();
}
public ObservableCollection<SItem2> PushPins
{
get
{
return _PushPins;
}
private set
{
_PushPins = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("PushPins"));
}
}
}
private GeoCoordinate _location;
public GeoCoordinate Location
{
get { return _location; }
set
{
if (_location != value)
{
_location = value;
}
}
}
private string _pinSource;
public string PinSource
{
get { return _pinSource; }
set
{
if (_pinSource != value)
{
_pinSource = value;
}
}
}
public void getItems()
{
var document = XDocument.Load("ListSmall.xml");
if (document.Root == null)
return;
var xmlns = XNamespace.Get("http://www.blahblah.co.uk/blah");
var events = from ev in document.Descendants("item")
select new
{
Latitude = Convert.ToDouble(ev.Element(xmlns + "Point").Element(xmlns + "lat").Value),
Longitude = Convert.ToDouble(ev.Element(xmlns + "Point").Element(xmlns + "long").Value),
};
ObservableCollection<SItem2> pushPinCollection = new ObservableCollection<SItem2>();
foreach (var ev in events)
{
SItem2 PushPin = new SItem2
( ev.Latitude, ev.Longitude)
{
};
pushPinCollection.Add(PushPin);
}
PushPins = pushPinCollection;
}
other class:
namespace observable_collection_test
{
public class SItem2
{
//public DateTimeOffset Date { get; set; }
//public string Title
//{ get; set; }
public double Latitude
{ get; set; }
public double Longitude
{ get; set; }
public GeoCoordinate Location
{ get; set; }
//public Uri Link { get; set; }
public SItem2(//string Title,
double Latitude, double Longitude)
{
//this.Date = Date;
//this.Title = Title;
this.Latitude = Latitude;
this.Longitude = Longitude;
//this.Location = Location;
//this.Link = Link;
}
}
Bit of XAML concerning adding pins to map:
<my:Map ZoomBarVisibility="Visible" ZoomLevel="10" CredentialsProvider="AhqTWqHxryix_GnWER5WYH44tFuutXNEPvFm5H_CvsZHQ_U7-drCdRDvcWSNz6aT" Height="508" HorizontalAlignment="Left" Margin="0,22,0,0" Name="map1" VerticalAlignment="Top" Width="456">
<my:MapItemsControl ItemsSource="{Binding PushPins}" >
<my:MapItemsControl.ItemTemplate>
<DataTemplate>
<my:Pushpin Background="Aqua" Location="{Binding Location}" ManipulationCompleted="pin_click">
</my:Pushpin>
</DataTemplate>
</my:MapItemsControl.ItemTemplate>
</my:MapItemsControl>
</my:Map>
It would also be good to know if I am approaching the pushpin binding to the maps in the right way.
It looks as if this is because you have used x:Name="PushPinItems" in your XAML which is the same name as one of your types, so when you think you are referencing your PushPinItems type in your codebehind, you are actually referencing the field that VS has generated for you from your XAML that represents that Pushpin instance. You could use a different x:Name in your XAML.
Update
Ok, I see the issue :) I haven't worked with the Bing maps control before, but looking at http://forums.silverlight.net/forums/t/197631.aspx (second post down), you need to set the map controls MapItemsControl property. The ItemsSource property here should be bound to your ObservableCollection of a custom type which contains properties such as Name and Location. You can then populate this collection with instances of this custom type (in the post they have used MapData as the type name).
You can also get more examples and source code at http://www.microsoft.com/maps/isdk/silverlight/