How to Bind an Observablecollection to a GridView? - c#

I am working on an App that selects Images and adds them to an Observablecollection. I want to show this collection in Xaml. There is no clear understandable answer that I have found so far.
in the MainPage:
public ObservableCollection<TimerWallpaper> timerWallpapers = new ObservableCollection<TimerWallpaper>();
And then the code for its class is this:
public class TimerWallpaper
{
public string timerFileName;
public BitmapImage timerImgSource;
public string timerTime;
public TimerWallpaper(string name, BitmapImage imgSource, int hours, int mins)
{
this.timerFileName = name;
this.timerImgSource = imgSource;
this.timerTime = hours.ToString() + " : " + mins.ToString();
}
}
Till this point it seems as the code is working.. the obstacle is with this code:
<GridView ItemsSource="x:Bind timerWallpapers">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:TimerWallpaper">
<Image Height="100" Width="100" Source="x:Bind timerImgSource"/>
<TextBlock Text="{x:Bind timerFileName}"/>
<TextBlock Text="{x:Bind timerTime}"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
I keep getting "Invalid Value" for the elements of the data templet.
What are the requirements to bind the GridView to the collection?
What is the correct format to do so?

OK, there are a number of problems with your code. First off, you should bind to properties, not fields, so your MainPage.cs should look something like this:
public sealed partial class MainPage : Page
{
public ObservableCollection<TimerWallpaper> TimerWallpapers { get; set; }
public MainPage()
{
this.InitializeComponent();
TimerWallpapers = new ObservableCollection<TimerWallpaper>();
DataContext = this;
}
}
and your TimerWallpaper like this:
public class TimerWallpaper
{
public string TimerFileName { get; set; }
public BitmapImage TimerImgSource { get; set; }
public string TimerTime { get; set; }
public TimerWallpaper(string name, BitmapImage imgSource, int hours, int mins)
{
this.TimerFileName = name;
this.TimerImgSource = imgSource;
this.TimerTime = hours.ToString() + " : " + mins.ToString();
}
}
(Or use private set if you want to)
Next, your binding syntax is wrong on a couple of lines where you forgot to enclose it in curly braces, and lastly, the DataTemplate can only have a single child, so you need to wrap your UI elements in a layout, e.g. a StackPanel, like so:
<GridView ItemsSource="{x:Bind TimerWallpapers}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:TimerWallpaper">
<StackPanel>
<Image Height="100" Width="100" Source="{x:Bind TimerImgSource}"/>
<TextBlock Text="{x:Bind TimerFileName}"/>
<TextBlock Text="{x:Bind TimerTime}"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>

Related

Changing the ViewModel's property from another ViewModel

I have the ListBox on my MainView.xaml, selecting the Item forces the ContentControl to display different UserControls. I use Caliburn.Micro library in this propgram. Here's some code:
<ListBox Grid.Row="1" Grid.Column="1" x:Name="ItemsListBox" SelectedItem="0" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding TextBlock1Text}" x:Name="TextBlock1"/>
<ContentControl Grid.Row="3" Grid.Column="1" Content="{Binding ElementName=ItemsListBox, Path=SelectedItem.Content}" />
The MainViewModel.cs:
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
NotifyOfPropertyChange(() => Name);
}
}
private string _textBlock1Text;
public string TextBlock1Text
{
get => _textBlock1Text;
set
{
_textBlock1Text = value;
NotifyOfPropertyChange(() => TextBlock1Text);
}
}
public MainViewModel()
{
TextBlock1Text = "Test";
Items = new ObservableCollection<ItemsModel>()
{
new ItemsModel { Name="Useless", Content=null },
new ItemsModel { Name="TextChangerViewModel", Content=new TextChangerViewModel(TextBlock1Text) }
};
}
public ObservableCollection<ItemsModel> Items { get; set; }
The ItemsModel.cs:
public class ItemsModel
{
public string Name { get; set; }
public object Content { get; set; }
}
And finally the TextChangerViewModel.cs:
public class TextChangerViewModel : Conductor<object>
{
private string _textBlock1Text;
public string TextBlock1Text
{
get => _textBlock1Text;
set
{
_textBlock1Text = value;
NotifyOfPropertyChange(() => TextBlock1Text);
}
}
public TextChangerViewModel(string textBlock1Text) //passing parameter from another ViewModel
{
TextBlock1Text = textBlock1Text;
}
}
So, the main question is how to change the TextBlock1Text (and the Text value of TextBlock in .xaml as well) in the MainViewModel.cs from the TextChangerViewModel.cs? I was thinking about using something like NotifyCollectionChanged on my Items ObservableCollection, but it work with collection of ItemsModel, not with the VM's, so I'm stuck here.
I'm also not sure if having public object Content { get; set; } in ItemsModel.cs is a good thing if I'm targeting the MVVM pattern, but I don't know the other way to do it (I'm very new to MVVM).
UPD
I'm looking for the property-changing way because I need to change the TextBlock1Text Text from another UserControl. Suppose I have the button on my TextChangerView.xaml: <Button Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="Change da text" cal:Message.Attach="ChangeTextButton"/>
And after the click on it I want the text on the parental MainView.xaml to change. But the thing is, I don't know how to change properties in this case, as I wrote above why.
Change the the binding of textblox1 to reference the selected item.
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ElementName=ItemsListBox, Path=SelectedItem.Name}" x:Name="TextBlock1"/>
or
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding ElementName=ItemsListBox, Path=SelectedItem.Content.TextBlock1Text}" x:Name="TextBlock1"/>

DataTemplate x:Bind namespace error

I have a question about Windows 10 UWP development using Visual Studio 2015.
I'm trying to use a DataTemplate for my GridView according to this tutorial. The problem I'm having is with my namespace.
I am not allowed to share my exact code for obvious reasons, but I'm wondering if one of you guys might have run into this before. I am getting almost the same error as this person (error code 0x09c4), except my DataTemplate is in my code-behind-file, not global like him/her. Along with that error I'm also getting the illusive "the _name does not exist in the namespace _namespace".
Here is a piece of my xaml file:
<Grid>
...
<GridView ItemsSource="{x:Bind ViewModel.AssessExItems}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:AssessExItem">
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
I know the DataTemplate is empty but even if I enter something there it still doesn't work. Here is my code-behind-file for this xaml file:
public sealed partial class AssessmentExample1Screen : Page
{
public AssessExItemViewModel ViewModel { get; set; }
public AssessmentExample1Screen()
{
this.InitializeComponent();
this.ViewModel = new AssessExItemViewModel();
}
}
public class AssessExItem
{
public int _assessment_id { get; set; }
public string name { get; set; }
public string surname { get; set; }
public string date { get; set; }
//public EmpAssessItem() { }
}
public class AssessExItemViewModel
{
private ObservableCollection<AssessExItem> exampleItems = new ObservableCollection<AssessExItem>();
public ObservableCollection<AssessExItem> AssessExItems { get { return this.exampleItems; } }
public AssessExItemViewModel()
{
//for (int i = 1; i < 3; i++)
//{
this.exampleItems.Add(new AssessExItem()
{
name = "Cat 777",
surname = "Botha",
date = "2015-03-22"
});
//}
this.exampleItems.Add(new AssessExItem()
{
name = "XZR 678",
surname = "Botha",
date = "2015-03-22"
});
this.exampleItems.Add(new AssessExItem()
{
name = "TBL 123",
surname = "Botha",
date = "2015-03-22"
});
}
}
Please help.
I reproduced your problem.
How to solve : Clean and build or rebuild the solution.And then I tested it,it works.
I guess the most possible reason of why it happened is build can update the file mainpage.g.cs which determined where to find the datatype.
<GridView ItemsSource="{x:Bind ViewModel.AssessExItems}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:AssessExItem">
<StackPanel Height="100" Width="100" Background="OrangeRed">
<TextBlock Text="{x:Bind name}"/>
<TextBlock Text="{x:Bind surname}" x:Phase="1"/>
<TextBlock Text="{x:Bind date}" x:Phase="2"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>

binding events to wpf list for realtime update

I am building a WPF app that will populate filtered headlines from a variety of news services. Each headline triggers an event, which in a console app I can display on the console. I want to use WPF here but have bot used it prior to this endeavor. My mainwindow xaml is as shown below. My original thought was to have an ObservableCollection populate list items in a listview in the xaml. If that is not the right approach, I'm open to expert opinion on a better way as speed of receipt to display is vital. If what I am doing is proper then how do I bind a new entry to the ObservableCollection to a new list item to display?
<StackPanel Orientation="Vertical" Margin="5,150 5 50" Name="HeadlinePanel">
<TextBlock Text="Filtered Headlines From Monitoring List"
HorizontalAlignment="Left" Margin="0,0 5 5" Name="ScrollingHeadlineLabel" FontWeight="Bold" FontSize="14" Background="LightSkyBlue" />
<ListBox>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline is from a website"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline is from TWTR"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="headline from a different website"/></TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="a property on the headline" />
<TextBlock><Run Text="text from a different tweet"/></TextBlock>
</StackPanel>
</ListBoxItem>
</ListBox>
</StackPanel>
In the console app the streaming begins (code shown below) in the filteredStream.Start() but the handler needs to register prior. In the console app I can write to the console (commented out) but here I add the headline object to the collection when the event fires. My question is how to bind that to my xaml list items. I will initiate the stream from mainwindow method? or some method I create to run within that?
var config = new TwitterOAuthConfig()
{
ConsumerKey = customerKey,
ConsumerSecret = customerSecret,
AccessToken = accessToken,
AccessTokenSecret = accessTokenSecret,
GeoOnly = false,
KeywordsToMonitor = keywords,
UsersToFollow = followers
};
var filteredStream = new TwitterClient(config);
var headlineCollection = new ObservableCollection<Headline>();
// subscribe to the event handler
filteredStream.HeadlineReceivedEvent +=
(sender, arguments) => headlineCollection.Add(arguments.Headline);
//Console.WriteLine("ID: {0} said {1}", arguments.Headline.Username, arguments.Headline.HeadlineText);
filteredStream.ExceptionReceived += (sender, exception) => Console.WriteLine(exception.HeadlineException.ResponseMessage);
filteredStream.Start();
Here is my Original HeadlineViewModel
public class HeadlineViewModel : ObservableItem
{
private string _headlineText;
public string Source { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
public List<string> UrlsParsedFromText { get; set; }
public string TimeStamp { get; set; }
}
I've updated it to the following:
public class HeadlineViewModel
{
public class HeadlineDisplayItems: ObservableItem
{
private string _headlineText;
public string HeadlineIconPath { get; set; }
public string TimeStamp { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
}
public List<string> UrlsParsedFromText { get; set; }
public ObservableCollection<HeadlineDisplayItems> HeadlineCollection { get; set; }
}
I don't know about your architecture, but wpf is mostly used with what they call MVVM (Model-View-ViewModel) where you have your View (you already posted the code), the ViewModel (I believe you don't have one) and the model (that is the Headline you are using). The objective of the ViewModel is to simplify the life of the view and make available all the information and actions it needs to display.
For example, you should hava a ViewModel for the whole view you are building, let's say "HeadlinePanelViewModel" (I don't recommend panel in the name because the idea of using a ViewModel is to abstract the controls or technologies being used). The HeadlinePanelViewModel needs to make the headlines available, so it must have a collection of a ViewModel representing all the information concerned to the headline (icons, titles, links, ...). In the end, you have an HeadlinePanelViewModel which contains an ObservableCollection. Set this as DataContext of your View and you must be ready to go to display your info.
Now comes the part of actually loading the info. Again, I don't know about your architecture. But in VERY simple terms, you could instantiate the filteredStream inside of your HeadlinePanelViewModel and everytime an HeadlineReceivedEvent is fired, you create an HeadlineViewModel corresponding to it and add to your collection.
"Complete" code based in the code in your answer:
The ViewModel:
public class HeadlineViewModel
{
public HeadlineViewModel()
{
// This is here only for simplicity. Put elsewhere
var config = new TwitterOAuthConfig()
{
ConsumerKey = customerKey,
ConsumerSecret = customerSecret,
AccessToken = accessToken,
AccessTokenSecret = accessTokenSecret,
GeoOnly = false,
KeywordsToMonitor = keywords,
UsersToFollow = followers
};
var filteredStream = new TwitterClient(config);
HeadlineCollection = new ObservableCollection<HeadlineDisplayItems>();
// subscribe to the event handler
filteredStream.HeadlineReceivedEvent +=
(sender, arguments) => HeadlineCollection.Add(ConvertToViewModel(arguments.Headline));
//Console.WriteLine("ID: {0} said {1}", arguments.Headline.Username, arguments.Headline.HeadlineText);
filteredStream.ExceptionReceived += (sender, exception) => Console.WriteLine(exception.HeadlineException.ResponseMessage);
filteredStream.Start();
}
private HeadlineDisplayItems ConvertToViewModel(Headline headline)
{
// Conversion code here
}
public class HeadlineDisplayItems: ObservableItem
{
private string _headlineText;
public string HeadlineIconPath { get; set; }
public string TimeStamp { get; set; }
public string Username { get; set; }
public string Text
{
get { return _headlineText; }
set
{
_headlineText = value;
RaisePropertyChangedEvent("HeadlineText");
}
}
}
public List<string> UrlsParsedFromText { get; set; }
public ObservableCollection<HeadlineDisplayItems> HeadlineCollection { get; set; }
}
The View:
<StackPanel Orientation="Vertical" Margin="5,150 5 50" Name="HeadlinePanel">
<TextBlock Text="Filtered Headlines From Monitoring List"
HorizontalAlignment="Left" Margin="0,0 5 5" Name="ScrollingHeadlineLabel" FontWeight="Bold" FontSize="14" Background="LightSkyBlue" />
<ListBox ItemsSource="{Binding HeadlineCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding HeadlineIconPath}" />
<TextBlock><Run Text="{Binding Text}"/></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
The code missing is where you do the this.DataContext = new HeadlineViewModel(); to the View.
EDIT: You may experience some problems with cross-thread operations if you try to update the observableCollection from a thread different of the view thread. A workaround is to use the solution in this link, but I don't think it's the best approach.
Create your ObservableCollection as a Property that you can Reference in XAML. Either create it directly in your MainWindow-Class or instantiate your collection as a StaticResource.
Bind your ObservableCollection as ItemsSource to your Listbox
<ListBox ItemsSource="{Binding Path=HeadlineCollection}"></ListBox>
and use an DataTemplate to bind your data to it
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image ... />
<TextBlock Text="{Binding Path=Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
For the Headline, create a data class that manages what you need to display (headline, icons, etc.). Something like this:
class Headline
{
bool isTwitter {get; set;}
string Text {get; set;}
}
Then in your client object you can simply add a new object to the ObservableCollection by calling the Add()-Method and the Application will automatically render the new object.
You can start your query client on the main UI thread but for a responsive UI you should let the query routine run in it's own thread (e.g. by using a BackgroundWorker) so that the UI isn't cluttered by it.

Bind List<Object> to a comboxbox in c# wpf

I have a static class named Building which contains a List<Beam> Beams as its property;
public static class Building
{
public static readonly List<Beam> Beams = new List<Beam>();
}
public class Beam
{
public string Story;
public double Elevation;
}
I'm trying to Bind the Building.Beams to a combobox in XAML so that Elevation and Story properties of each item in Building.Beams list is displayed in different columns in the combobox. I have been able to implement the two columns, I just can't Bind these properties.
Here is what I have tried so far:
<ComboBox x:Name="cmbBuilding" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Width="300">
<TextBlock Width="150" Text="{Binding Path=Story }"/>
<TextBlock Width="150" Text="{Binding Path=Elevation}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
var b1 = new Beam { Elevation = 320, Story = "ST1" };
var b2 = new Beam { Elevation = 640, Story = "ST2" };
Building.Beams.Add(b1);
Building.Beams.Add(b2);
First of all you can't bind with fields.
Convert Story and Elevation to properties (automatic properties in your case will do)
public class Beam
{
public string Story { get; set;}
public double Elevation { get; set;}
}
Second, you should use ObservableCollection in case you are adding items to the list after loading finishes so that UI gets notification.
public static readonly ObservableCollection<Beam> Beams
= new ObservableCollection<Beam>();
Try this example:
XAML
<Grid>
<ComboBox x:Name="cmbBuilding" Width="100" Height="25" ItemsSource="{Binding Path=Beams}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Width="300">
<TextBlock Width="150" Text="{Binding Path=Story}" HorizontalAlignment="Left" />
<TextBlock Width="150" Text="{Binding Path=Elevation}" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Add item" VerticalAlignment="Top" Click="Button_Click" />
</Grid>
Code-behind
public partial class MainWindow : Window
{
Building building = new Building();
public MainWindow()
{
InitializeComponent();
building.Beams = new List<Beam>();
building.Beams.Add(new Beam
{
Elevation = 320,
Story = "ST1"
});
this.DataContext = building;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var b1 = new Beam { Elevation = 320, Story = "ST1" };
var b2 = new Beam { Elevation = 640, Story = "ST2" };
building.Beams.Add(b1);
building.Beams.Add(b2);
cmbBuilding.Items.Refresh();
}
}
public class Building
{
public List<Beam> Beams
{
get;
set;
}
}
public class Beam
{
public string Story
{
get;
set;
}
public double Elevation
{
get;
set;
}
}
Some notes
When you use properties in the Binding, you need to be properties with get and set, not fields.
Properties, what were added to the List<T> will automatically update, you should call MyComboBox.Items.Refresh() method, or use ObservableCollection<T>:
ObservableCollection represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
Is it maybe because you have declared Beams as readonly yet you try to ADD items to it? Beams is also defined as a variable, try removing the readonly and making it a property with a getter and setter

WPF - Binding to collection in object

I was trying to get it working for few days.
What is wrong in this code?
This is my window XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Rapideo_Client"
x:Class="Rapideo_Client.MainWindow"
Title="NVM" SnapsToDevicePixels="True" Height="400" Width="625">
<Window.Resources>
<DataTemplate x:Key="linksTemplate" DataType="DownloadLink">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"></TextBlock>
<Label Content="{Binding Path=SizeInMB}"/>
<Label Content="{Binding Path=Url}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
x:Name="MainListBox"
ItemTemplate="{DynamicResource linksTemplate}">
</ListView>
</Window>
This is my class:
class Rapideo
{
(...)
public List<DownloadLink> Links { get; private set; }
(...)
}
This is my item:
class DownloadLink
{
public string Name { get; private set; }
public string Url { get; private set; }
public DateTime ExpiryDate { get; private set; }
public float SizeInMB { get; private set; }
public int Path { get; private set; }
public string Value { get; private set; }
public LinkState State { get; set; }
public enum LinkState
{
Ready, Downloading, Prepering, Downloaded
}
public DownloadLink(string name, string url, DateTime expiryDate, float sizeInMB, int path, string value, LinkState state)
{
Name = name;
Url = url;
ExpiryDate = expiryDate;
SizeInMB = sizeInMB;
Path = path;
Value = value;
State = state;
}
}
This is my binding:
RapideoAccount = new Rapideo();
MainListBox.ItemsSource = RapideoAccount.Links;
Later in the code I populate that list in RapideoAccount.Links.
But nothing is showing in ListView.
List View is always empty.
Where is mistake in that code?
Yes, it should be an ObservableCollection<DownloadLink> if you're planning on adding to it AFTER you have setup the ItemsSource. If the list is preloaded and you won't be changing it, List<T> would have worked.
Now I do think that
MainListBox.ItemsSource = RapideoAccount.Links;
is still technically a binding. But what you are probably thinking of is binding via the DataContext rather than directly (al la MVVM style). So that'd be:
RapideoAccount = new Rapideo();
this.DataContext = RapideoAccount;
Then in your window, you'd bind your ItemSource like this:
<Window
...
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
x:Name="MainListBox"
ItemsSource="{Binding Links}"
ItemTemplate="{DynamicResource linksTemplate}">
</ListView>
</Window>
First off, you should use an ObservableCollection<DownloadLink> rather than a List<DownloadLink> if you're planning on making changes to the list after setting up the binding.
Second of all, just to be clear:
MainListBox.ItemsSource = RapideoAccount.Links;
is not a binding. You're just setting the property. That will work for certain scenarios, but its not really a binding like we normally talk about in WPF.
I think that Links needs to be an ObservableCollection, not a List.

Categories

Resources