UWP RadExpanderControl is binding ContentTemplate but not ExpandableContentTemplate - c#

the idea here is that every revision of a file spawns a new file with a name that adds "_v1", "_v2", etc. The back end gets the files from disk and groups them based on that name. So a header might say "example", then when you click on the expander, you see "example", "example_v1", "example_v2", etc.
However, when this grouped data reaches the front-end, only the keys are showing, with an empty list of expanded content. Is my XAML messed up or is it something else?
This is MainPage.xaml:
<my:RadDataBoundListBox x:Name="listBox">
<my:RadDataBoundListBox.ItemTemplate>
<DataTemplate>
<my1:RadExpanderControl
Content="{ Binding }"
ExpandableContent="{ Binding }"
IsExpanded="True">
<my1:RadExpanderControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{ Binding Key }" />
</DataTemplate>
</my1:RadExpanderControl.ContentTemplate>
<my1:RadExpanderControl.ExpandableContentTemplate>
<DataTemplate>
<TextBlock Text="{ Binding Name }" />
</DataTemplate>
</my1:RadExpanderControl.ExpandableContentTemplate>
</my1:RadExpanderControl>
</DataTemplate>
</my:RadDataBoundListBox.ItemTemplate>
</my:RadDataBoundListBox>
This is from MainPage.xaml.cs:
public sealed partial class MainPage : Page
{
CollectionViewSource cvs = null;
public MainPage()
{
InitializeComponent();
cvs = new CollectionViewSource();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
cvs.Source = ViewModel.Files;
cvs.IsSourceGrouped = true;
listBox.ItemsSource = (IOrderedEnumerable<IGrouping<string, Files>>)cvs.Source;
}
}
This is from the ViewModel:
private IOrderedEnumerable<IGrouping<string, Files>> files;
public IOrderedEnumerable<IGrouping<string, Files>> Files
{
get
{
if (files == null)
{
var source = Source;
string[] separaters = { "_", "." };
files = from file in source
group file by file.Name.Split(separaters, StringSplitOptions.RemoveEmptyEntries)[0]
into g
orderby g.Key
select g;
}
return files;
}
}
This is how the list of filenames is pulled from disk:
public ObservableCollection<Files> Source
{
get
{
return GetFilesData(Folder).Result;
}
}
This is the model for the filenames:
public class Files
{
public string Name { get; set; }
}

Related

Binding User control elements to Model properties

I'm trying to bind a UserControl to some model with no luck.
I have this ItemsControl:
<ItemsControl x:Name="spPatientFiles" Grid.Row="2"
ItemsSource="{Binding PatientFiles, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type uc:PatientFile}">
<uc:PatientFile/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.DataContext>
<ViewModels:MainViewModel/>
</ItemsControl.DataContext>
</ItemsControl>
It is bound by
public class MainViewModel
{
public ObservableCollection<PatientFile> PatientFiles { get; set; }
public MainViewModel()
{
PatientFiles = new ObservableCollection<PatientFile>();
if (!string.IsNullOrEmpty(Models.LocalSettings.SaveFolder) &&
System.IO.Directory.Exists(Models.LocalSettings.SaveFolder))
{
var files = System.IO.Directory.GetFiles(Models.LocalSettings.SaveFolder);
foreach (var p in files)
{
var n = p.Substring(p.LastIndexOf("\\") + 1, p.LastIndexOf(".") - p.LastIndexOf("\\") - 1);
var ext = p.Substring(p.LastIndexOf(".") + 1);
PatientFiles.Add(new PatientFile()
{
FileName = n,
Path = p,
FileType = ext == "avi" ? OutputType.Video : OutputType.Image
});
}
}
}
}
Main xaml has View Model:
<Window ...
xmlns:ViewModels="clr-namespace:Octopus.Capturing.ViewModels"x:Class="Octopus.Capturing.Views.MainApp"
...>
This is my UserControl (uc:PatientFile)
<UserControl ...
xmlns:Models="clr-namespace:Octopus.Capturing.Models"
x:Class="Octopus.Capturing.Views.Controls.PatientFile"
... >
<Label FontSize="10"
VerticalContentAlignment="Center" Foreground="White"
Content="{Binding FileName}"/>
But the binding doesn't work.
When I run the app I get the structure of the UserControl (style and correct number of files read from folder) but without the binding to the file name property.
What i'm doing wrong here?
You should not have a collection of PatientFile UserControls in a view model. Instead, create a separate view model for this kind of user control, PatientFileViewModel. Implement INotifyPropertyChanged to enable notifying the controls to update the changed properties.
public class PatientFileViewModel : INotifyPropertyChanged
{
private string _fileName;
public string FileName
{
get => _fileName;
set
{
if (_fileName == value)
return;
_fileName = value;
OnPropertyChanged();
}
}
// ...implement the other properties like this, too.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Adapt your ItemTemplate to display the PatientFile user control for the PatientFileViewModel.
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type uc:PatientFileViewModel}">
<uc:PatientFile/>
</DataTemplate>
</ItemsControl.ItemTemplate>
Next, remove the properties and code that is now in the view model from your PatientFile user control. You can keep its XAML code. The content of the Label will now be bound to the data context, which is the PatientFileViewModel that is set by the ItemsControl automatically when applying the DataTemplate.
Finally adapt your MainViewModel to create and use PatientFileViewModels instead of PatientFiles.
public class MainViewModel
{
public ObservableCollection<PatientFileViewModel> PatientFiles { get; set; }
public MainViewModel()
{
PatientFiles = new ObservableCollection<PatientFileViewModel>();
if (!string.IsNullOrEmpty(Models.LocalSettings.SaveFolder) &&
System.IO.Directory.Exists(Models.LocalSettings.SaveFolder))
{
var files = System.IO.Directory.GetFiles(Models.LocalSettings.SaveFolder);
foreach (var p in files)
{
var n = p.Substring(p.LastIndexOf("\\") + 1, p.LastIndexOf(".") - p.LastIndexOf("\\") - 1);
var ext = p.Substring(p.LastIndexOf(".") + 1);
PatientFiles.Add(new PatientFileViewModel()
{
FileName = n,
Path = p,
FileType = ext == "avi" ? OutputType.Video : OutputType.Image
});
}
}
}
}
Make sure that you set the DataContext of your main view to the MainViewModel, e.g.:
<Window xmlns:ViewModels="clr-namespace:Octopus.Capturing.ViewModels"x:Class="Octopus.Capturing.Views.MainApp"
...>
<Window.DataContext>
<ViewModels:MainViewModel/>
</Window.DataContext>
<!-- ...other code. -->
</Window>
Furthermore, you should remove Mode=TwoWay and UpdateSourceTrigger=PropertyChanged on the ItemsSource binding, it is not needed.

Why TreeView doesn't list my subdirectories? [duplicate]

I am trying to get my head around Heirarchical DataTemplates and TreeViews in WPF and am having some trouble.
I have created an app with only a TreeView on the form as below and defined HierarchicalDataTemplate's for both a Directory object and a File object I then Bind the TreeView to the Directories property (ObservableCollection) of my model.
<Grid>
<Grid.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Directory}" ItemsSource ="{Binding Directories}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:File}" ItemsSource ="{Binding Files}">
<TextBlock Text="{Binding Path=FileName}"/>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView Margin="12,12,0,12" Name="treeView1" HorizontalAlignment="Left" Width="204" >
<TreeViewItem ItemsSource="{Binding Directories}" Header="Folder Structure" />
</TreeView>
</Grid>
This works in that in the TreeView I see my directories and it recursively displays all sub directories, but what I want to see is Directories and Files! I've checked the model and it definately has files in some of the sub directories but I can't see them in the tree.
I'm not sure if it is my template that is the problem or my model so I have included them all! :-)
Thanks
OneShot
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private MainWindowViewModel _vm;
public MainWindowViewModel VM
{
set
{
_vm = value;
this.DataContext = _vm;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var d = new Directory() { Name = "temp" };
recurseDir("c:\\temp", ref d);
VM = new MainWindowViewModel( new List<Directory>() { d } );
}
private void recurseDir(string path, ref Directory dir)
{
var files = System.IO.Directory.GetFiles(path);
var dirs = System.IO.Directory.GetDirectories(path);
dir.Name = path.Substring(path.LastIndexOf("\\")+1);
for (int i = 0; i < files.Length; i++)
{
var fi = new FileInfo(files[i]);
dir.Files.Add(new File() {
FileName = System.IO.Path.GetFileName(files[i]),
DirectoryPath = System.IO.Path.GetDirectoryName(files[i]),
Size = fi.Length,
Extension= System.IO.Path.GetExtension(files[i])
});
}
for (int i = 0; i < dirs.Length; i++)
{
var d = new Directory() { Name = dirs[i].Substring(dirs[i].LastIndexOf("\\")+1) };
recurseDir(dirs[i], ref d);
dir.Directories.Add(d);
}
}
}
-
public class MainWindowViewModel
: DependencyObject
{
public MainWindowViewModel(List<Directory> Dirs)
{
this.Directories = new ObservableCollection<Directory>( Dirs);
}
public ObservableCollection<Directory> Directories
{
get { return (ObservableCollection<Directory>)GetValue(DirectoriesProperty); }
set { SetValue(DirectoriesProperty, value); }
}
public static readonly DependencyProperty DirectoriesProperty =
DependencyProperty.Register("Directories", typeof(ObservableCollection<Directory>), typeof(MainWindowViewModel), new UIPropertyMetadata(null));
public Directory BaseDir
{
get { return (Directory)GetValue(BaseDirProperty); }
set { SetValue(BaseDirProperty, value); }
}
public static readonly DependencyProperty BaseDirProperty =
DependencyProperty.Register("BaseDir", typeof(Directory), typeof(MainWindowViewModel), new UIPropertyMetadata(null));
}
-
public class Directory
{
public Directory()
{
Files = new List<File>();
Directories = new List<Directory>();
}
public List<File> Files { get; private set; }
public List<Directory> Directories { get; private set; }
public string Name { get; set; }
public int FileCount
{
get
{
return Files.Count;
}
}
public int DirectoryCount
{
get
{
return Directories.Count;
}
}
public override string ToString()
{
return Name;
}
}
-
public class File
{
public string DirectoryPath { get; set; }
public string FileName { get; set; }
public string Extension { get; set; }
public double Size { get; set; }
public string FullPath
{
get
{
return System.IO.Path.Combine(DirectoryPath, FileName);
}
}
public override string ToString()
{
return FileName;
}
}
Take a look at this again:
<HierarchicalDataTemplate DataType="{x:Type local:Directory}" ItemsSource ="{Binding Directories}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:File}" ItemsSource ="{Binding Files}">
<TextBlock Text="{Binding Path=FileName}"/>
</HierarchicalDataTemplate>
What you're saying is that if you encounter an object of type File, display it with a text block and get its children from a property Files under the File. What you really want is for the Files to show up under each Directory, so you should create a new property that exposes both Directories and Files:
public class Directory
{
//...
public IEnumerable<Object> Members
{
get
{
foreach (var directory in Directories)
yield return directory;
foreach (var file in Files)
yield return file;
}
}
//...
}
and then your template becomes:
<HierarchicalDataTemplate DataType="{x:Type local:Directory}" ItemsSource ="{Binding Members}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:File}">
<TextBlock Text="{Binding Path=FileName}"/>
</DataTemplate>
UPDATE:
Actually, the above is not sufficient if you want to receive collection changed notifications for the Members. If that's the case, I recommend creating a new ObservableCollection and adding Directory and File entries to it in parallel to adding to the Files and Directories collections.
Alternately, you may wish to reconsider how you store your information and put everything in a single collection. The other lists are then simply filtered views of the main collection.

Cannot get Silverlight HierarchicalDataTemplate to display IsolatedStorage folders

This is my first foray into Hierarchical data and am having a bit of a problem.
In Silverlight 4, I am trying to get a list of isolated storage folders to display in a TreeView. Nothing displays at all. My Treeview is completely blank. What am I missing? I am getting data and it is correct.
Any help would be appreciated.
XAML
<sdk:TreeView x:Name="FolderTreeView" Grid.Column="0" Margin="0,0,3,0" ItemsSource="{Binding _Folders}">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Folders}">
<TextBlock Margin="0" Text="{Binding Name, Mode=OneWay}"/>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
CS
internal class Folder
{
public Folder() { Folders = new List<Folder>(); }
public string Name { get; set; }
public List<Folder> Folders { get; set; }
}
private List<Folder> _Folders = new List<Folder>();
public OpenFileDialog()
{
InitializeComponent();
ifs = IsolatedStorageFile.GetUserStoreForApplication();
var folder = new Folder
{
Name = "Root",
Folders = (from c in ifs.GetDirectoryNames()
select new Folder
{
Name = c,
Folders = LoadFolders(c)
}).ToList()
};
_Folders.Add(folder);
FolderTreeView.DataContext = new { _Folders };
}
private List<Folder>LoadFolders(string folderName)
{
if(folderName == null)
return null;
return (from c in ifs.GetDirectoryNames(folderName + "\\*.*")
select new Folder
{
Name = c,
Folders = LoadFolders(c)
}).ToList();
}
Thanks
A few things
<sdk:TreeView x:Name="FolderTreeView" Grid.Column="0" Margin="0,0,3,0"
ItemsSource="{Binding _Folders}">
you can't bind to private members.
You need to use ObservableCollections instead of Lists. The binding manager effectively listens for the CollectionChanged events fired by ObservableCollection and notifies the bound controls.
You'll want to implement INotifyPropertyChanged and raise PropertyChanged notifications in your property setters.
Finally, have you set the DataContext for the Treeview?
Also, look in your Output debug window for errors relating to binding.
Edit, ok try:
FolderTreeView.DataContext = this;
and wrap _Folders in a property
public ObservableCollection <Folder> Folders
{
get
{
return _Folders;
}
set
{
_Folders = value;
OnPropertyChanged("Folders");
}
}
change your binding to
<sdk:TreeView x:Name="FolderTreeView" Grid.Column="0" Margin="0,0,3,0"
ItemsSource="{Binding Folders}">
I've changed things quite a bit,
public class Folder : INotifyPropertyChanged
{
public Folder(string folderName)
{
Name = folderName;
Folders = new ObservableCollection<Folder>();
var _ifs = IsolatedStorageFile.GetUserStoreForApplication();
if (folderName != null)
{
Folders = new ObservableCollection<Folder>(
(from c in _ifs.GetDirectoryNames(folderName + "\\*.*")
select new Folder(folderName + "\\" + c)
));
}
else
{
Folders = new ObservableCollection<Folder>(
(from c in _ifs.GetDirectoryNames()
select new Folder(folderName + "\\" + c)
));
}
}
public string Name { get; set; }
private ObservableCollection<Folder> _Folders;
public ObservableCollection<Folder> Folders
{
get { return _Folders; }
set { _Folders = value; OnPropertyChanged("RootFolder"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public partial class OpenFileDialog : UserControl
{
public OpenFileDialog()
{
InitializeComponent();
RootFolder = new Folder (null);
RootFolders = new ObservableCollection<Folder>();
RootFolders.Add(RootFolder);
FolderTreeView.DataContext = this;
}
private Folder _RootFolder;
public Folder RootFolder
{
get { return _RootFolder; }
set { _RootFolder = value; }
}
private ObservableCollection<Folder> _RootFolders;
public ObservableCollection<Folder> RootFolders
{
get { return _RootFolders; }
set { _RootFolders = value; }
}
}
xaml
<sdk:TreeView x:Name="FolderTreeView" Margin="0,0,3,0" ItemsSource="{Binding RootFolders}">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Folders}">
<StackPanel>
<TextBlock Margin="0" Text="{Binding Name, Mode=OneWay}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
Ok...problem solved. Don't understand why though.
I had the ChildWindow set as internal scope instead of public scope because I didn't want the window itself to be viewed external of my Silverlight class library. If anyone can answer why this would stop Hierarchical data binding but not standard data binding I would like to know.

Cannot correctly populate ListPicker control with Image and Name

I have created a ListPicker control for a user to change his or her background, but not all of the information is populated correctly in the ListPicker control. The problem arises when a user navigated to my SettingsPage, the text of all ListPicker items is displayed properly, but only the image of the currently selected background is shown. All other image backgrounds are blank. Furthermore, the weird thing is as I change the image backgrounds and navigate back and forth between the MainPage and the SettingsPage, every new image background that is selected will then show up in the ListPicker (along with all other previously selected backgrounds) while the backgrounds that haven't been selected do not have images shown in the ListPicker. So far what I have is as follows:
SettingsPage.xaml
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme" Grid.Row="2" Grid.ColumnSpan="2"
SelectedIndex="{Binding}"
SelectionChanged="ThemeListPicker_SelectionChanged">
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Width="50" Height="37.59" Margin="0,0,12,0"/>
<TextBlock Text="{Binding Name}" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
</toolkit:ListPicker>
SettingsPage.xaml.cs
List<ThemeItem> themeList;
public SettingsPage()
{
InitializeComponent();
themeList = new List<ThemeItem>()
{
new ThemeItem { Image = new BitmapImage(new Uri("Resources/Themes/PanoramaBackground.png", UriKind.Relative)), Name = "Default" },
new ThemeItem { Image = new BitmapImage(new Uri("Resources/Themes/Abstract Pattern.jpg", UriKind.Relative)), Name = "Abstract Pattern" },
new ThemeItem { Image = new BitmapImage(new Uri("Resources/Themes/Asian Beauty.jpg", UriKind.Relative)), Name = "Asian Beauty" },
new ThemeItem { Image = new BitmapImage(new Uri("Resources/Themes/Autumn Leaf.jpg", UriKind.Relative)), Name = "Autumn Leaf" },
new ThemeItem { Image = new BitmapImage(new Uri("Resources/Themes/Old Barn.png", UriKind.Relative)), Name = "Old Barn" }
};
ThemeListPicker.ItemsSource = themeList;
ThemeListPicker.DataContext = ThemeListPicker.SelectedIndex;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
//Respect the saved Theme index setting
this.ThemeListPicker.SelectedIndex = Settings.ThemeIndex.Value;
}
private void ThemeListPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count <= 0) //to eliminate IndexOutOfRangeException
{
return;
}
//string selectedItem = e.AddedItems[0] as string;
ThemeItem selectedItem = e.AddedItems[0] as ThemeItem;
if (selectedItem != null)
{
Settings.Theme.Value = selectedItem.Image.UriSource.ToString();
Settings.ThemeIndex.Value = ThemeListPicker.SelectedIndex;
}
}
where ThemeItem is a small custom class
public class ThemeItem
{
public BitmapImage Image { get; set; }
public string Name { get; set; }
}
How would I be able to properly load all Image backgrounds and respective text Names in the ListPicker control when the SettingsPage is navigatedTo?
EDIT: added Settings class info
public class Settings
{
//Theme settings
public static readonly Setting<int> ThemeIndex = new Setting<int>("ThemeIndex", 0);
//Theme Background
public static readonly Setting<string> Theme = new Setting<string>("Theme", "Resources/Themes/PanoramaBackground.png");
//public static readonly Setting<BitmapImage> Theme = new Setting<BitmapImage>("Theme", new Uri("/Resources/Themes/PanoramaBackground.png", UriKind.Relative));
}
//Encapsulates a key/value pair stored in Isolated Storage ApplicationSettings
public class Setting<T>
{
string name;
T value;
T defaultValue;
bool hasValue;
public Setting(string name, T defaultValue)
{
this.name = name;
this.defaultValue = defaultValue;
}
public T Value
{
get
{
//Check for the cached value
if (!this.hasValue)
{
//Try to get the value from Isolated Storage
if (!IsolatedStorageSettings.ApplicationSettings.TryGetValue(
this.name, out this.value))
{
//It has not been set yet
this.value = this.defaultValue;
IsolatedStorageSettings.ApplicationSettings[this.name] = this.value;
}
this.hasValue = true;
}
return this.value;
}
set
{
//Save the value to Isolated Storage
IsolatedStorageSettings.ApplicationSettings[this.name] = value;
this.value = value;
this.hasValue = true;
}
}
public T DefaultValue
{
get { return this.defaultValue; }
}
//"Clear" cached value
public void ForceRefresh()
{
this.hasValue = false;
}
}
Since you get the large list of items in the ListPicker, you need to create a template for it too. You only have the ItemTemplate at the moment. The property is called FullModeItemTemplate and the example of how to do that is shown here:
http://windowsphonegeek.com/articles/listpicker-for-wp7-in-depth
I ended up using the FullModeItemTemplate so that the the ListPicker would populate correctly in another page.
SettingsPage.xaml
<Grid.Resources>
<DataTemplate x:Name="PickerItemTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Width="50" Height="37.59" Margin="0,0,12,0"/>
<TextBlock Text="{Binding Name}" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Name="PickerFullModeItemTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}" Width="50" Height="37.59" Margin="0,0,12,0"/>
<TextBlock Text="{Binding Name}" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme" Grid.Row="2" Grid.ColumnSpan="2"
FullModeItemTemplate="{StaticResource PickerFullModeItemTemplate}"
ItemTemplate="{StaticResource PickerItemTemplate}"
SelectedIndex="{Binding}"
SelectionChanged="ThemeListPicker_SelectionChanged"/>

Passing binded listbox data to pivot page? Windows Phone 7

I am having an absolute headache figuring this out. I badly need some help with this.
I have a listbox populated with items called with a public static void RSS feed class. Once the listbox populates with the databound items, I click on an item and it passes it through to my pivot page. However, when I flick left or right, all I get is the same image. That is my problem, and what I would like to have happen is if the user flicks left, it loads the previous RSS image. I would like it to also go to the next picture if the If the user scrolls right.
The community has been helpful in providing links to some things, or saying to not use the listbox, etc. However while I am new to all of this, I would just like concrete help with the code i have to achieve what I have in mind. It's nothing personal -- I just need to take babysteps with this before I get worked up with other things I have no clue about.
Here is all my relevant code.
Page 1 Xaml:
<ListBox x:Name="listbox" HorizontalContentAlignment="Stretch" ItemsSource="{Binding items}" SelectionChanged="listbox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Stretch="Fill" Height="60" Width="85" Source="{Binding Url}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Page1 C# Code Behind:
namespace Imaged
{
public partial class UserSubmitted : PhoneApplicationPage
{
private const string Myrssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public UserSubmitted()
{
InitializeComponent();
//This next function calls the RSS service, and returns the (items) and binds it to
//{listbox.ItemsSource = items;}. I am unable to reference the count of the items, or
//the array of it for some reason? The images load once the page loads.
RssService.GetRssItems(Myrssfeed, (items) => { listbox.ItemsSource = items; }, (exception) => { MessageBox.Show(exception.Message); }, null);
}
}
}
Once the listbox fills I am now trying to pass the selection by the user to a pivot page. I want that same image to show up in the pivot, and when the user pivots left or right, it shows the previous image or next image in the collection.
The Pivot Page I am trying to pass this to, XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<controls:Pivot Title="{Binding Title}">
<!--Pivot item one-->
<controls:PivotItem x:Name="item1">
<Image Source="{Binding Url}"/> <!--I take it this is causing the pics to be the same?-->
</controls:PivotItem>
<!--Pivot item two-->
<controls:PivotItem x:Name="item2">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
<!--Pivot item three-->
<controls:PivotItem x:Name="item3">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
</controls:Pivot>
</Grid>
The RSS Service Class being called:
namespace WindowsPhone.Helpers
{
public class RssService
{
public static void GetRssItems(string rssFeed, Action<IList<RssItem>> onGetRssItemsCompleted = null, Action<Exception> onError = null, Action onFinally = null)
{
WebClient webClient = new WebClient();
// register on download complete event
webClient.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
{
try
{
// convert rss result to model
IList<RssItem> rssItems = new List<RssItem>();
Stream stream = e.Result;
XmlReader response = XmlReader.Create(stream);
{
SyndicationFeed feeds = SyndicationFeed.Load(response);
foreach (SyndicationItem f in feeds.Items)
{
RssItem rssItem = new RssItem(f.Title.Text, f.Summary.Text, f.PublishDate.ToString(), f.Links[0].Uri.AbsoluteUri);
rssItems.Add(rssItem);
}
}
// notify completed callback
if (onGetRssItemsCompleted != null)
{
onGetRssItemsCompleted(rssItems);
}
}
finally
{
// notify finally callback
if (onFinally != null)
{
onFinally();
}
}
};
webClient.OpenReadAsync(new Uri(rssFeed));
}
}
}
and finally the RSSItem Class:
namespace WindowsPhone.Helpers
{
public class RssItem
{
public RssItem(string title, string summary, string publishedDate, string url)
{
Title = title;
Summary = summary;
PublishedDate = publishedDate;
Url = url;
// Get plain text from html
PlainSummary = HttpUtility.HtmlDecode(Regex.Replace(summary, "<[^>]+?>", ""));
}
public string Title { get; set; }
public string Summary { get; set; }
public string PublishedDate { get; set; }
public string Url { get; set; }
public string PlainSummary { get; set; }
}
}
Disclaimer: I don't think that binding this many items to a Pivot control is necessarily the right thing to do. Your mileage may vary, but I think a more virtualized solution would be more efficient. For my tests, it seemed to perform OK, but my little voice tells me that there be dragons here...
I recreated your project to the best of my ability and made some enhancements to get it to do what you wanted. Basically, the trick was using a ViewModel that was shared between both the main list page (UserSubmitted.xaml) and the page with the Pivot items on it (PivotPage1.xaml). By setting both page's DataContext property to the same object, we were able to bind both lists to the same source, thus eliminating the need to pass anything around.
In App.xaml.cs:
public static ViewData ViewModel { get; private set; }
private void Application_Launching(object sender, LaunchingEventArgs e)
{
// note: you should properly Tombstone this data to prevent unnecessary network access
ViewModel = new ViewData();
}
Here is how ViewData is defined:
public class ViewData : INotifyPropertyChanged
{
private string _FeedTitle;
private RssItem _SelectedItem = null;
private ObservableCollection<RssItem> _feedItems = new ObservableCollection<RssItem>();
private const string MyRssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public ViewData()
{
RssService.GetRssItems(
MyRssfeed,
(title, items) =>
{
App.Current.RootVisual.Dispatcher.BeginInvoke(() =>
{
FeedTitle = title;
FeedItems = new ObservableCollection<RssItem>(items);
});
},
(exception) =>
{
MessageBox.Show(exception.Message);
},
null);
}
public ObservableCollection<RssItem> FeedItems
{
get { return _feedItems; }
set
{
if (_feedItems == value)
return;
_feedItems = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedItems"));
}
}
public string FeedTitle
{
get { return _FeedTitle; }
set
{
if (_FeedTitle == value)
return;
_FeedTitle = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedTitle"));
}
}
public RssItem SelectedItem
{
get { return _SelectedItem; }
set
{
if (_SelectedItem == value)
return;
_SelectedItem = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(sender, args);
}
}
Once this is established, it's relatively easy to wire up both page's data context properties to App.ViewModel.
Last item was the scrolling and positioning of the selected item when navigating. When you select an item from the list page, the SelectedItem property of the shared ViewModel is bound to the SelectedItem property on the ListBox. After navigation to the details page, we have to find the selected item in the pivot and make it visible:
public PivotPage1()
{
InitializeComponent();
Loaded += (sender, e) =>
{
this.DataContext = App.ViewModel;
var selectedItem = App.ViewModel.SelectedItem;
var pi = ItemPivot.Items.First(p => p == selectedItem);
ItemPivot.SelectedItem = pi;
};
}
Setting the SelectedItem property of the Pivot control scrolls the pivot to the proper item and makes it visible.
The full sample is posted at http://chriskoenig.net/upload/imaged.zip if you want to see it in action.
If I got you correctly, you need to bind listbox in following way:
<ListBox ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
And then bind Pivot in same way:
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
Try the following for the pivot (based on Alex's code)
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}">
<Pivot.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Url}"/>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
It assumes on the pivot page DataContext there is the same object "items" providing access to all the feeditems, and a property SelectedFeed which (as Alex mentioned) supports INotifyPropertyChanged

Categories