My UWP is required to have a Favorites page that allows the user to reorder and save the data on the page. Originally my data comes from a large JSON file which is deserialized using Newtonsoft's Json.net, and is stored for this page in a Dictionary which then fills the public ObservableCollection.
This is where I now get lost, setting the ObservableCollection as the DataContext and then using the data as a Binding in the XAML code to populate the GridView with all the Titles, Subtitles and Images that each Item requires.
In theory this should work, but in my trials and tests the page remains blank while all the C# code behind the scenes makes it seem like it should be populated.
I don't know why the page is not filling to I am turning to the collective help of all of you.
P.S: I don't really care about the neatness of this code, I just want to get it working.
XAML File
<Page
x:Name="pageRoot"
x:Class="Melbourne_Getaway.FavouritesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Melbourne_Getaway"
xmlns:data="using:Melbourne_Getaway.Data"
xmlns:common="using:Melbourne_Getaway.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<x:String x:Key="AppName">Favourites</x:String>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Padding="60,136,116,46"
SelectionMode="None"
IsSwipeEnabled="false"
CanReorderItems="True"
CanDragItems="True"
AllowDrop="True"
ItemsSource="{Binding Items}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="250" Height="107">
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" />
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" Height="30" Margin="15,0,15,0" FontWeight="SemiBold" />
<TextBlock Text="{Binding Group}" Foreground="{ThemeResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" TextWrapping="NoWrap" Margin="15,-15,15,10" FontSize="12" />
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Margin="39,59,39,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
Style="{StaticResource NavigationBackButtonNormalStyle}"
VerticalAlignment="Top"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" />
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1"
IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40" />
</Grid>
</Grid>
CS File
using Melbourne_Getaway.Common;
using Melbourne_Getaway.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.Storage;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Melbourne_Getaway
{
public sealed partial class FavouritesPage : Page
{
public ObservableCollection<ItemData> Items { get; set; }
private ObservableDictionary defaultViewModel = new ObservableDictionary();
private NavigationHelper navigationHelper;
private RootObject jsonLines;
private StorageFile fileFavourites;
private Dictionary<string, ItemData> ItemData = new Dictionary<string, ItemData>();
public FavouritesPage()
{
loadJson();
getFavFile();
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
}
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
private async void loadJson()
{
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///DataModel/SampleData.json"));
string lines = await FileIO.ReadTextAsync(file);
jsonLines = JsonConvert.DeserializeObject<RootObject>(lines);
feedItems();
}
private async void getFavFile()
{
Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
fileFavourites = await storageFolder.GetFileAsync("MelbGetaway.fav");
}
private async void feedItems()
{
if (await FileIO.ReadTextAsync(fileFavourites) != "")
{
foreach (var line in await FileIO.ReadLinesAsync(fileFavourites))
{
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
if (Item.UniqueId == line)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
}
}
}
}
}
else
{//should only execute if favourites file is empty, first time use?
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
await FileIO.AppendTextAsync(fileFavourites, Item.UniqueId + "\r\n");
}
}
}
setupObservableCollection();
}
public ObservableDictionary DefaultViewModel
{
get { return this.defaultViewModel; }
}
#region NavigationHelper loader
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
private async void MessageBox(string Message)
{
MessageDialog dialog = new MessageDialog(Message);
await dialog.ShowAsync();
}
private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var sampleDataGroups = await SampleDataSource.GetGroupsAsync();
this.defaultViewModel["Groups"] = sampleDataGroups;
}
#endregion NavigationHelper loader
#region NavigationHelper registration
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
navigationHelper.OnNavigatedFrom(e);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
navigationHelper.OnNavigatedTo(e);
}
#endregion NavigationHelper registration
}
public class ItemData
{
public string UniqueID { get; set; }
public string Title { get; set; }
public string Group { get; set; }
public string ImagePath { get; set; }
}
}
Without a good Minimal, Complete, and Verifiable code example it is impossible to know for sure what's wrong. However, one glaring error does appear in your code:
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
In your XAML, you bind to {Binding Items}. With the DataContext set to the Items property value, the correct binding would actually be just {Binding}.
Alternatively, if you want to keep the XAML the way it is, you would have to set DataContext = this; instead. Of course, if you did it that way, then you would run into the problem that you don't appear to be raising INotifyPropertyChanged.PropertyChanged, or even implementing that interface. You can get away with that if you are sure the property will be set before the InitializeComponent() method is called, but in the code you've shown that does not appear to be the case.
So if you want to set the binding to {Binding Items} you also need to implement INotifyPropertyChanged and make sure you raise the PropertyChanged event with the property name "Items" when you actually set the property.
If the above does not address your question, please improve the question by providing a good MCVE that reliably reproduces the problem.
I figured it out. my problem lied in the way I was trying to pass the Data to the page itself. Instead of using DataContext = Items;and trying to access the data that way. I instead set the direct ItemsSource for the GridView.
The end result was simply changing DataContext = Items to itemGridView.ItemsSource = Items;
Related
I have a MainWindowViewModel and my MainWindow contains a frame to display project pages.
The first page being displayed is a list of recently opened projects(Similar to Microsoft word) which has it's own ViewModel.
There is no problem in loading the list but when I want to send the user-selected item from this list to the MainWindowViewModel I can not use Find-Ancestor to reach the Window DataContext(It looks like the frame has some restrictions).
How can I send the user-selected item to the MainWindowViewModel?
public class RecentlyOpenedFilesViewModel
{
readonly IFileHistoryService _fileHistoryService;
private ObservableCollection<RecentlyOpenedFileInfo> _RecentlyOpenedFilesList;
public ObservableCollection<RecentlyOpenedFileInfo> RecentlyOpenedFilesList
{
get { return _RecentlyOpenedFilesList; }
set { _RecentlyOpenedFilesList = value; RaisePropertyChanged(); }
}
public RecentlyOpenedFilesViewModel( IFileHistoryService fileService):base()
{
_fileHistoryService = fileService;
RecentlyOpenedFilesList=new ObservableCollection<RecentlyOpenedFileInfo>(_fileHistoryService.GetFileHistory());
}
public void RefreshList()
{
RecentlyOpenedFilesList = new ObservableCollection<RecentlyOpenedFileInfo>(_fileHistoryService.GetFileHistory());
}
}
<Page
x:Class="MyProject.Views.V3.Other.RecentlyOpenedFilesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Views.V3.Other"
xmlns:vmv3="clr-namespace:MyProject"
Title="RecentlyOpenedFilesPage">
<Page.Resources>
<DataTemplate x:Key="RecentlyOpenedFileInfoTemplate"
>
<Button
Height="70"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.OpenProjectFromPathCommand}"
CommandParameter="{Binding}">
<Button.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Top">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Path}" />
</StackPanel>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="50,0,0,0"
VerticalAlignment="Center"
Text="{Binding DateModified}" />
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView
ItemTemplate="{StaticResource RecentlyOpenedFileInfoTemplate}"
ItemsSource="{Binding RecentlyOpenedFilesList}" />
</Grid>
public RecentlyOpenedFilesPage(MainWindowViewModel vm)
{
this.DataContext = vm;
InitializeComponent();
}
Now I have a direct link between MainWindowViewModel and RecentlyOpenedFilesViewModel but I would like to remove this dependency and use another way of connection like(routed commands which I have a problem with)
The MainWindow contains a frame in which the RecentlyOpenedFilesPage is set to its content.
<Window
x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fw="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF" >
<Frame Name="frameMain"/></Window>
public class MainWindowViewModel : RecentlyOpenedFilesViewModel, IMainWindowViewModel
{
private void LoadRecentlyOpenedProjects()
{
CurrentView = new RecentlyOpenedFilesPage(this);
}
}
So, here is my suggested solution. It uses the basic idea to propagate the DataContext from the outside into a frame content, as presented in page.DataContext not inherited from parent Frame?
For demonstration purpose, I provide an UI with a button to load the page, a textblock to display the selected result from the list within the page and (ofcourse) the frame that holds the page.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Name="parentGrid">
<TextBlock VerticalAlignment="Top" HorizontalAlignment="Right" Margin="5" Text="{Binding SelectedFile}" Width="150" Background="Yellow"/>
<Button VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5" Click="Button_Click" Width="150">Recent Files List</Button>
<Frame Name="frameMain" Margin="5 50 5 5"
LoadCompleted="frame_LoadCompleted"
DataContextChanged="frame_DataContextChanged"/>
</Grid>
</Window>
Viewmodel classes:
public class BaseVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyWindowVm : BaseVm
{
private string _selectedFile;
public string SelectedFile
{
get => _selectedFile;
set
{
_selectedFile = value;
OnPropertyChanged();
}
}
}
public class MyPageVm : BaseVm
{
public ObservableCollection<MyRecentFile> Files { get; } = new ObservableCollection<MyRecentFile>();
}
public class MyRecentFile
{
public string Filename { get; set; }
public string FilePath { get; set; }
}
Main code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
parentGrid.DataContext = new MyWindowVm();
}
// Load Page on some event
private void Button_Click(object sender, RoutedEventArgs e)
{
frameMain.Content = new RecentlyOpenedFilesPage(new MyPageVm
{
Files =
{
new MyRecentFile { Filename = "Test1.txt", FilePath = "FullPath/Test1.txt"},
new MyRecentFile { Filename = "Test2.txt", FilePath = "FullPath/Test2.txt"}
}
});
}
// DataContext to Frame Content propagation
private void frame_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
UpdateFrameDataContext(sender as Frame);
}
private void frame_LoadCompleted(object sender, NavigationEventArgs e)
{
UpdateFrameDataContext(sender as Frame);
}
private void UpdateFrameDataContext(Frame frame)
{
var content = frame.Content as FrameworkElement;
if (content == null)
return;
content.DataContext = frame.DataContext;
}
}
Now, the page.xaml ... notice: we will set the page viewmodel to the pageRoot.DataContext, not to the page itself. Instead we expect the page datacontext to be handled from the outside (as we do in the MainWindow) and we can reference it with the page internal name _self:
<Page x:Class="WpfApplication1.RecentlyOpenedFilesPage"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="RecentlyOpenedFilesPage"
Name="_self">
<Grid Name="pageRoot">
<ListView ItemsSource="{Binding Files}"
SelectedValue="{Binding DataContext.SelectedFile,ElementName=_self}"
SelectedValuePath="FilePath"
DisplayMemberPath="Filename"/>
</Grid>
</Page>
Page code behind to wire up the viewmodel:
public partial class RecentlyOpenedFilesPage : Page
{
public RecentlyOpenedFilesPage(MyPageVm myPageVm)
{
InitializeComponent();
pageRoot.DataContext = myPageVm;
}
}
As you can see, with this setup, no viewmodel knows about any involved view. The page doesn't handle the MainViewmodel, but the page requires a DataContext with a SelectedFile property to be provided from the outside.
The MainViewmodel doesn't know about the recent file list, but allows to set a selected file, no matter where it originates from.
The decision, to initialize the RecentlyOpenedFilesPage with a pre-created viewmodel is not important. You could just as well use internal logic to initialize the page with recent files, then the Mainwindow would not be involved.
Cannot load a reference assembly for execution
This is the only information i get when i debug/compile my project. The project compiles and runs and i can use the features i have added with no problem.
The error is on the Page.Datacontext where i define what viewmodel to use on that page and its the same for the two pages i have.
This is the error
This is where the error is
It happened to me in another project also, and i tried debugging/finding help but i couldnt figure it out how to fix it. It seemed that i had some problems with 64/32bit clash in my assemblies. So I started over in another project to see if I could fix it, and it work for a good while. I dont understand how this error showed up in my 2nd project. I just tried to do some xaml binding, i copied some code bits in, didnt work, so i took it out and then the error came. The project still builds and work, "Buld succeeded" but the error persists.
As i tested more, I dont get the error if I change the viewmodel to lets say MainPageViewModel. I am using template 10 hamburger
<Page
x:Class="Gainz.Views.ExercisePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Behaviors="using:Template10.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:controls="using:Template10.Controls"
xmlns:local="using:Gainz.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:Gainz.Models"
xmlns:vm="using:Gainz.ViewModels"
mc:Ignorable="d">
<Page.DataContext>
<vm:ExercisePageViewModel x:Name="ViewModel" />
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="Exercise" x:DataType="data:Exercise">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<!-- <Image Name="image" Source="{x:Bind Image}" HorizontalAlignment="Center" Width="150" /> -->
<StackPanel Margin="20,20,0,0">
<TextBlock Text="{x:Bind ExerciseName }" HorizontalAlignment="Left" FontSize="18" />
<TextBlock Text="{x:Bind Category}" HorizontalAlignment="Left" FontSize="14" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<RelativePanel>
<controls:PageHeader x:Name="pageHeader" RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignTopWithPanel="True" Text="Exercises">
<!-- secondary commands -->
<controls:PageHeader.SecondaryCommands>
<AppBarButton Click="{x:Bind ViewModel.GotoSettings}" Label="Settings" />
<AppBarButton Click="{x:Bind ViewModel.GotoPrivacy}" Label="Privacy" />
<AppBarButton Click="{x:Bind ViewModel.GotoAbout}" Label="About" />
</controls:PageHeader.SecondaryCommands>
</controls:PageHeader>
</RelativePanel>
<RelativePanel>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Width="600">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<ListView Name="ex" ItemsSource="{x:Bind ViewModel.Exercises}"
ItemClick="{x:Bind ViewModel.ListView_ItemClick}"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource Exercise}">
</ListView>
</Grid>
</RelativePanel>
</StackPanel>
using Template10.Mvvm;
using System;
using System.ComponentModel;
using System.Net.Http;
using System.Collections.ObjectModel;
using Gainz.Models;
using Windows.UI.Xaml.Controls;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
namespace Gainz.ViewModels
{
public class ExercisePageViewModel : ViewModelBase, INotifyPropertyChanged
{
public ObservableCollection<Exercise> Exercises { get; set; } = new ObservableCollection<Exercise>();
public new event PropertyChangedEventHandler PropertyChanged;
public ExercisePageViewModel()
{
LoadExercises();
}
private async void LoadExercises()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(#"http://localhost:33048/api/");
var json = await client.GetStringAsync("Exercises");
Exercise[] exercises = JsonConvert.DeserializeObject<Exercise[]>(json);
Exercises.Clear();
foreach (var excercise in exercises)
{
Exercises.Add(excercise);
}
}
}
private string name;
public string Name
{
get { return name; }
set
{
if (value != this.name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
private string description;
public string Description
{
get { return description; }
set
{
if (value != this.description)
{
description = value;
NotifyPropertyChanged("Description");
}
}
}
private string category;
public string Category
{
get { return category; }
set
{
if (value != category)
{
category = value;
NotifyPropertyChanged("Category");
}
}
}
public void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void GotoDetailsPage() =>
NavigationService.Navigate(typeof(Views.DetailPage));
public void GotoSettings() =>
NavigationService.Navigate(typeof(Views.SettingsPage), 0);
public void GotoPrivacy() =>
NavigationService.Navigate(typeof(Views.SettingsPage), 1);
public void GotoAbout() =>
NavigationService.Navigate(typeof(Views.SettingsPage), 2);
}
}
I'm facing a problem in my WPF project at the moment. At this moment I have a Viewmodel which has a Manager (to communicate with the repo).
internal class TicketViewModel
{
private TicketManager mgr;
public IEnumerable<Ticket> List { get; set; }
public TicketViewModel()
{
mgr = new TicketManager();
List = mgr.GetTickets();
}
}
I've managed to bind this list to the Listbox in my MainWindow. The next step is that I need to add an extra ticket to the list and also pass this through the manager. The problem is I need two parameters from some Controls in the MainWindow. From MVVM perspective I need to use bound Commands on e.g. a Button to communicate with the viewmodel as my viewmodel can't/may not access controls from the window. Is using parameterized Commands the way to go here?
The next problem is that the Listbox won't update I guess. This is the code:
<ListBox x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I found that using a CompareableCollection is the way to go here, but then I still have to read all the Tickets again after adding a new Ticket.
Thanks in advance,
Hicy
okay here is the code.
Lets say you have three textboxes on MainWindow(since you have three Textblocks.) so Your MainWindow.xaml looks like
<Window.DataContext>
<local:MyViewModel/>--set's your viewModel
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="250*"/>
<RowDefinition Height="90"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="TicketNumber" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=Text}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="Text" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=State}" />
<TextBox x:Name="State" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=TicketNumber}" />
<Button Content="Button" Command="{Binding Path=MainCommand}" Grid.Row="2"/>
</Grid>
and I am assuming that you have some class called class Ticket which contain these three members
Class Ticket
{
public int TicketNumber { get; set; }
public string Text { get; set; }
public string State { get; set; }
}
Now in class TicketManager we fill it with some dummy data
class TicketManager
{
ObservableCollection<Ticket> tl = new ObservableCollection<Ticket>();
internal ObservableCollection<Ticket> GetTickets()
{
tl.Add(new Ticket() { State = "State1", Text = "Text1", TicketNumber = 1 });
tl.Add(new Ticket() { State = "State2", Text = "Text2", TicketNumber = 2 });
tl.Add(new Ticket() { State = "State3", Text = "Text3", TicketNumber = 3 });
return tl;
}
}
and in your Mainwindow ViewModel lets call it MyViewModel.cs we add
class MyViewModel:INotifyPropertyChanged
{
private TicketManager mgr;
public ObservableCollection<Ticket> List { get; set; }
private string text;
private string state;
private int ticketNumber;
private readonly DelegateCommand<object> MyButtonCommand;
public Class1()
{
mgr = new TicketManager();
List = mgr.GetTickets();
MyButtonCommand = new DelegateCommand<object>((s) => { AddListToGrid(text, state, ticketNumber); }, (s) => { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(state); });
}
private void AddListToGrid(string text, string state, int ticketNumber)
{
List.Add(new Ticket() {Text=text,State=state,TicketNumber=ticketNumber });
}
public DelegateCommand<object> MainCommand
{
get
{
return MyButtonCommand;
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged("State");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public int TicketNumber
{
get
{
return ticketNumber;
}
set
{
ticketNumber = value;
OnPropertyChanged("TicketNumber");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can Modify the code in anyway you want
This ViewModel implements fewthings which are very important from MVVM point of view
1) INotifyPropertyChanged
2) WPF Delegate Command
P.S:The code is tested and it runs as expected
Don't get hung up on MVVM it is simply a separation of data from a view, and models are shared between the two with a majority of the business logic (on a shared component) should be performed on the VM; it is not a religion just a three tiered data system. IMHO
If your button needs to do an operation, have it make a call, most likely in the code behind, to a method on the VM which handles the business logic, updates the list with the new item and notifies the manager.
I would bind the list in question to an ObservableCollection which can notify upon insert/delete of an item.
When I update a ObservableCollection<string> and call RaisePropertyChanged(..) somehow its content is not shown within my Listbox in WPF. I have no idea what I am doing wrong.
The critical part is where I update the FileNames property:
public class HistoricalDataViewRawDataViewModel : ViewModelBase
{
private string _currentDirectory;
private ObservableCollection<string> _fileNames;
private List<string> _rawData;
public ICommand ChangeDirectoryCommand { get; private set; }
public string CurrentDirectory
{
get { return _currentDirectory; }
set
{
if (_currentDirectory != value)
{
_currentDirectory = value;
RaisePropertyChanged("CurrentDirectory");
}
}
}
public ObservableCollection<string> FileNames
{
get { return _fileNames; }
set
{
if (_fileNames != value)
{
_fileNames = value;
RaisePropertyChanged("FileNames");
}
}
}
public List<string> RawData
{
get { return _rawData; }
set
{
if (_rawData != value)
{
_rawData = value;
RaisePropertyChanged("RawData");
}
}
}
public HistoricalDataViewRawDataViewModel()
{
ChangeDirectoryCommand = new RelayCommand(ChangeDirectory);
var fileDirectory = Properties.Settings.Default.HistoricalData_RawDataSourceDirectory;
//set current directory
CurrentDirectory = fileDirectory;
//load all fileNames
LoadAvailableFileNames(fileDirectory);
}
private void ChangeDirectory()
{
using (var folderDialog = new FolderBrowserDialog())
{
folderDialog.SelectedPath = CurrentDirectory;
folderDialog.ShowDialog();
//set current directory
CurrentDirectory = folderDialog.SelectedPath;
//save current directory to settings
Properties.Settings.Default.HistoricalData_RawDataSourceDirectory = CurrentDirectory;
Properties.Settings.Default.Save();
//load files in chosen directory
LoadAvailableFileNames(CurrentDirectory);
}
}
private void LoadAvailableFileNames(string directory)
{
FileNames = new ObservableCollection<string>(FileIO.GetFileNamesInDirectory(directory, false, true));
}
private async void LoadRawData(string fileName)
{
}}
This is the xaml code. It should work as is because I look to display a ObservableCollection<string>. I added couple items to the listbox from code-behind and it displayed just fine:
DataContext="{Binding HistoricalDataViewRawDataViewModel, Source={StaticResource Locator}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<ToggleButton Margin="10" HorizontalAlignment="Left" VerticalAlignment="Center" Content="Choose Directory" FontSize="18" Foreground="White" Command="{Binding ChangeDirectoryCommand}"/>
<TextBlock
Margin="10"
FontSize="18"
Foreground="White"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
TextAlignment="Center"
Text="{Binding CurrentDirectory}"/>
</StackPanel>
<dxdo:DockLayoutManager Grid.Row="1">
<dxdo:LayoutGroup Orientation="Vertical">
<dxdo:LayoutPanel ItemHeight="7*">
<dxdo:LayoutControlItem>
<ListBox Name="MyListBox" ItemsSource="{Binding FileNames}"/>
</dxdo:LayoutControlItem>
</dxdo:LayoutPanel>
<dxdo:LayoutPanel Caption="Activity Log" ItemHeight="200" >
<dxdo:LayoutControlItem>
<ListBox/>
</dxdo:LayoutControlItem>
</dxdo:LayoutPanel>
</dxdo:LayoutGroup>
</dxdo:DockLayoutManager>
</Grid>
According to this support ticket in DevExpress, simply removing the LayoutControlItem works.
I created a sample project using your XAML and some dummy data and it works fine:
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
Title="MainWindow" Height="350" Width="525">
<dxdo:DockLayoutManager Grid.Row="1">
<dxdo:LayoutGroup Orientation="Vertical">
<dxdo:LayoutPanel ItemHeight="7*">
<!-- notice the removal of LayoutControlItem here -->
<ListBox ItemsSource="{Binding FileNames}"/>
</dxdo:LayoutPanel>
<dxdo:LayoutPanel Caption="Activity Log" ItemHeight="200" >
<ListBox/>
</dxdo:LayoutPanel>
</dxdo:LayoutGroup>
</dxdo:DockLayoutManager>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Dummy data
DataContext = new
{
FileNames = Enumerable.Range(0, 5).Select(x => "File" + x.ToString())
};
}
}
Result:
I assume these dxdo-controls are non-standard controls? I assume these controls break the inheritance mechanism. The inheritance mechanism applies to all FrameworkElement objects. It means that the property values are inherited along the logical tree. This enables you to set the datacontext property on the outer Grid but use it, for example, on the TextBlock. The value that you set on the Grid is inherited by the StackPanel and then again by the TextBlock. This only works for objects that inherit from FrameworkElement.
Can you set the datacontext directly on the ListBox? . If this works, than this indicates that the inhitance context is broken.
I have a problem that resists the past few hours, here is the ViewModel code: (PS: I can not share the url stream but do not worry its march because I tested it with BreakPoint)
private ObservableCollection<CustomerPublic> customers;
List<CustomerPublic> liste = new List<CustomerPublic>();
public ObservableCollection<CustomerPublic> Customers
{
get
{ return customers; }
set
{
if (customers != value)
{
customers = value;
RaisePropertyChanged("Customers");
}
}
}
private int id;
public int ID
{
get
{
return id;
}
set
{
id = value;
RaisePropertyChanged("ID");
}
}
public Detail_AgenceViewModel(int id)
{
this.ID = id;
PopulateCollection();
}
public Detail_AgenceViewModel()
{
}
private void PopulateCollection()
{
ParseFeedRequest();
}
private void ParseFeedRequest()
{
RestClient client = new RestClient();
client.BaseUrl = "....";
RestRequest request = new RestRequest();
.......
client.ExecuteAsync(request, ParseFeedCallBack);
}
public void ParseFeedCallBack(IRestResponse response)
{
if (response.StatusCode == HttpStatusCode.OK)
{
ParseXMLFeed(response.Content);
}
}
private void ParseXMLFeed(string feed)
{
if (feed == null)
return;
XElement xmlItems = XElement.Parse(feed);
liste = (from response in xmlItems.Descendants("result")
let lib = response.Element("lib")
let adresse = response.Element("adresse")
select new CustomerPublic
{
lib = lib == null ? null : lib.Value,
adresse = adresse == null ? null : adresse.Value,
}).ToList();
Customers = new ObservableCollection<CustomerPublic>(liste);
}
the View:
<phone:PhoneApplicationPage.DataContext>
<vm:Detail_AgenceViewModel/>
</phone:PhoneApplicationPage.DataContext>
<Grid x:Name="LayoutRoot"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel"
Grid.Row="0"
Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle"
Text="MY APPLICATION"
Style="{StaticResource PhoneTextNormalStyle}" />
<TextBlock x:Name="PageTitle"
Text="page name"
Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}" />
</StackPanel>
<!--ContentPanel - place additional content here-->
<StackPanel x:Name="ContentPanel" Grid.Row="2" Margin="12,0,12,0" Orientation="Vertical">
<!--TextBox Text="{Binding Count, Mode=TwoWay}" x:Name="tbCount" />
<TextBlock Text="{Binding Count}" /-->
<ListBox x:Name="Agences" ItemsSource="{Binding Customers}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding lib}" />
<TextBlock Text="{Binding adresse}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
The problem is that its all going well Customers even she is loaded but nothing appears! someone has an idea?
You are setting Customers to a new instance of an observable collection!
When you change the observable collection to a new instance, you must use INotifyPropertyChanged to tell the view that the collection has changed to a new instance - although changes to the items IN the collection are notified, changes to the collection ITSELF are not.
When you do this:
Customers = new ObservableCollection<CustomerPublic>(liste);
The view is still bound to the OLD collection. You should do:
Customers.Clear();
foreach(var item in liste)
Customers.Add(item);
OR make sure that the Customers property calls the NotifyPropertyChanged function.
Have a check of this video or article for more info:
http://www.codeproject.com/Articles/371217/Apex-Part-1-Create-Your-First-MVVM-Application
http://www.youtube.com/watch?v=m4cx9w5fiwk&feature=youtu.be
Good luck and please do let me know if this helps!!
I'm having similar problems ;
public void FillList(List<StockItem> siList)
{
listBox.ItemsSource = siList;
}
Where sIList is a filled list of X items, with correctly named properties.
Program builds & runs fine, but the listbox isnt shown. (this problem started when transitioning into MVVM)
I've got it.
Check your datacontext - I bet it is null. I've had this exact same issue in WP7. In the constructor of your PhoneApplicationPage do:
DataContext = new Detail_AgenceViewModel();
and initialise it there. In WP7 when I create the datacontext in XAML it's null. Does this help?