I'm using RestSharp to get a response from an API. And working properly. But after I tried to get those response in to model and bind it with a CollectionView using CommunityToolkit.MVVM, it's not displaying data.
XAML file
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:NewAppliedLeave">
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
ViewModel.cs
[ObservableProperty]
ObservableCollection<NewAppliedLeave> _LHItems;
public async Task<ObservableCollection<NewAppliedLeave>> GetAppliedLeave()
{
RestResponse response = await client.PostAsync(request);
var responseContent = response.Content.ToString();
Debug.WriteLine(responseContent);
List<NewAppliedLeave> leaveItem = JsonConvert.DeserializeObject<List<NewAppliedLeave>>(responseContent);
LHItems = new ObservableCollection<NewAppliedLeave>(leaveItem);
return LHItems;
}
I'm expecting to display response content in CollectionView.
At first, please add a break point when you debug to check if the LHItems has a correcr value.
And then please make sure you have bound the CollectionView.ItemSource to the LHItems in the xaml or in the page's construction method.
Finally, I didn't see there are any controls in the <DataTemplate>. Did you add some controls in it such as Label or Image in it to display the properties in the model named NewAppliedLeave and make it inherit from the ObservableObject?
And for the ObservableCollection<NewAppliedLeave> property in the viewmodel, you need't to add [ObservableProperty].
You can refer to this case which shows the details about using the CommunityToolkit.mvvm and the CollectionView in the .net maui.
When using [ObservableProperty] MVVM toolkit will generate code for you but the declaration should be lowercase without underscore.
UPDATE: After some testing, a leading underscore is not making any issue.
[ObservableProperty]
ObservableCollection<NewAppliedLeave> lHItems;
The above will generate the below code automatically:
public NewAppliedLeave LHItems
{
get => lHItems;
set => SetProperty(ref lHItems, value);
}
Then you can use the below code to populate the list.
LHItems = new ObservableCollection<NewAppliedLeave>(leaveItem);
Related
I'm fairly new to C# but understand basic concepts.
I'm currently working on a Uni assignment where I have to have multiple textboxes be entered as a single entry in a listbox, then save all entries to a text file. I also need to be able to load the text file and add new entries to the list.
I've figured out how to save data to a .txt file, as well as reloading the .txt file back into the listbox using
if (File.Exists("PersonalFile.txt"))
{
string[] line = File.ReadAllLines("PersonalFile.txt");
lbxStaffDetails.ItemsSource = line;
}
However, doing it this way I can't add new entries to the listbox due to the data binding, I get this error message System.InvalidOperationException: 'Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.'
Is there a way to remove the binding but keep the data in the listbox? Using lbxStaffDetails.ItemsSource = null; clears the listbox; or is there another way to read all lines of the .txt file to the listbox without using the file as a binding source?
Notes:
lbxStaffDetails is this listbox in question
PersonalFile.txt is the .txt holding the entries on new lines.
This is the first time I've bound data and files.
Edit:
Forgot to mention how I'm adding the data to the listbox so here's the code for that.
private void btnAddWaitingList_Click(object sender, RoutedEventArgs e)
{
_EmployeeID = tbxEmployeeID.Text;
_Name = tbxName.Text;
_PayRate = tbxPayRate.Text;
_Email = tbxEmail.Text;
string employeeDetails = _EmployeeID + "," + _Name + "," + _PayRate + "," + _Email;
lbxStaffDetails.Items.Add(employeeDetails);
}
As the code fires and gets to the bottom line it throws the error mentioned above.
Don't confuse data binding with simple value assignment. Data binding is a different concept, where a target binds to a data source using a Binding. A Binding will monitor target and source and delegates changes from one to the other. It's a bi-directional dynamic data link.
You can setup a Binding in XAML or C# (see Data binding overview in WPF).
You are not binding the ListBox to a file. You have read contents of a file to an array of strings. This array is then assigned to the ListBox.ItemsSource property.
Since you have populated the ListBox using the ItemsSource property, you are not allowed to modify its items using the Items property (InvalidOperationException).
You have to either assign the modified collection again to ListBox.ItemsSource (which will cause the complete ListBox to create all items again, which is bad for the performance) or make use of ObservableCollection.
It's a special collection that allows to be observed (Observer pattern). The observer gets notified by the observed collection via an event that the collection has changed (add/move/remove). Every ItemsControl is able to listen to this event and will automatically update itself.
MainWindow.xaml.cs
partial class MainWindow : Window
{
public ObservableCollection<string> StaffDetails { get; set; }
public MainWindow()
{
InitializeComponent();
// Set the DataContext to MainWindow for data binding (XAML version)
this.DataContext = this;
}
private void ReadFile()
{
if (!File.Exists("PersonalFile.txt"))
{
return;
}
string[] lines = File.ReadAllLines("PersonalFile.txt");
// Create a new ObservableCollection and initialize it with the array
this.StaffDetails = new ObservableCollection<string>(lines);
// You write to the file using this same collection directly,
// without accessing the ListBox
File.WriteAllLines("PersonalFile.txt", this.StaffDetails);
// Option 1: assignment (static data link)
this.lbxStaffDetails.ItemsSource = this.StaffDetails;
// Alternative option 2: C# data binding (dynamic data link)
var binding = new Binding(nameof(this.StaffDetails)) { Source = this };
this.lbxStaffDetails.SetBinding(ItemsControl.ItemsSourceProperty, binding);
// Alternative option 3 (recommended): XAML data binding (dynamic data link). See MainWindow.xaml
}
private void btnAddWaitingList_Click(object sender, RoutedEventArgs e)
{
_EmployeeID = tbxEmployeeID.Text;
_Name = tbxName.Text;
_PayRate = tbxPayRate.Text;
_Email = tbxEmail.Text;
var employeeDetails = $"{_EmployeeID},{_Name},{_PayRate},{_Email}";
// Modify the ObservableCollection.
// Since ListBox is observing this collection, it will automatically update itself
this.StaffDetails.Add(employeeDetails);
}
}
MainWindow.xaml
<Window>
<!-- Alternative option 3: XAML data binding (recommended) -->
<ListBox x:Name="lbxStaffDetails"
ItemsSource="{Binding StaffDetails}" />
</Window>
I have a carousel view displaying a list of images, and this list will change, and I can't make the carousel view adding or deleting the images.
XAML :
<CarouselView x:Name="main_carousel">
<CarouselView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding .}"/>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
My list of images's path is added to the item source in the constructor of the main page
C# :
public static List<string> imagesList;
public MainPage()
{
InitializeComponent();
imagesList = new List<string>();
main_carousel.ItemsSource = imagesList;
}
At first the carousel is empty, but then I add some paths to my list, like this for instance :
C# :
public void addElementToMainCarousel()
{
imagesList.add(absolute_path + "myImage1.jpg");
imagesList.add(absolute_path + "myImage2.jpg");
}
The carousel view doesn't display anything if I add the paths in the list afterwards. But it works if I add the paths in the list before the first main_carousel.ItemsSource = imagesList; .
I tried to re-set the ItemsSource like this :
C# :
public void addElementToMainCarousel()
{
imagesList.add(absolute_path + "myImage1.jpg");
imagesList.add(absolute_path + "myImage2.jpg");
main_carousel.ItemsSource = imagesList;
}
but it doesn't work.
Is there a way to force refresh of the carousel view or do you know anything that can help me ?
For some reason, the CarouselView won't re-draw itself on screen even if you update your imagesList. You need to re-assign CarouselView.ItemSource property with a new List<string> or Array containing your updated data. Please, see a sample below :
myCarouselView.ItemSource = imageslist.ToArray();
This works for me as ToArray() method returns a new object with the updated data, copied from the previous List.
Whenever you want to use a list that binds both the view and the viewmodel, you must implement ObservableCollection, not a list.
ObservableCollection reflects your changes from side to side.
ObservableCollection<string> myList = new ObservableCollection<string>();
mylist.Add(yourString);
listview.itemSource = mylist;
I have a app that retrieves comments from a website. I can programmatically add them to a StackPanel, calulating their indentation for comment replies but I'd like to learn how to bind a list of comments to a ListView and have it display correctly there.
My Comment Class looks like this:
class Comment
{
public List<Comment> Replies { get; set; }
public string Body { get; }
public int Level { get; set; }
public Comment(string BodyText)
{
Body = BodyText;
}
public Comment(string BodyText, List<Comment> replies, int level)
{
Body = BodyText;
Replies = replies;
Level = level;
}
}
So each Comment can have a List<> of comments (replies) to it and the Level variable indicates the depth of the comment.
What would be the process to set up a ListView so that I can bind a list of comments to it and those comments replies to those and so on? Or is there a better way to do this?
Thank you.
This is how I currently have it implemented which is visually correct but I'd like to use data binding rather than doing it through code.
Create a ListView, bind it's ItemsSource property to the list of top level comments. Use an ItemTemplate that contains the comment and another ListView in a vertical StackPanel. That inner ListView needs to get the same ItemTemplate it is in. I'm not sure if {StaticResource} will handle that, but it should.
If you use ObservableCollections this will actually be dynamic.
I recommend you to use the third party package WinRTXamlToolkit. Which contains a TreeView control that can meet your hierarchy requirements well. You can just bind the comments collection to the TreeView control code behind. Code example as follows:
Xaml Code
<controls:TreeView Width="400" MaxHeight="400" x:Name="Treeviewcomment">
<controls:TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Body}"/>
<data:DataTemplateExtensions.Hierarchy>
<data:HierarchicalDataTemplate ItemsSource="{Binding Replies}" />
</data:DataTemplateExtensions.Hierarchy>
</DataTemplate>
</controls:TreeView.ItemTemplate>
</controls:TreeView>
Binding code
this.InitializeComponent();
ObservableCollection<Comment> comments = new ObservableCollection<Comment>
{
new Comment ("By the way,I have noticed that ..."),
new Comment("Has this been metioned anywhere before..",
new List<Comment>
{
new Comment("Delta upgrade..."),
new Comment("When only stuff that...",
new List<Comment> {
new Comment("That's blloby...")},
3)},
2),
new Comment("Just had to turn off..")
};
And the result:
Special nuget package for uwp: WinRT XAML Toolkit. And I also upload the above code example to GitHub.
I need to get the DataContext of the View set by using ContentSource property of the ModernWindow, Could you please help.I am using MVVM framework with Modern UI. The ViewModel code from where I need to show another window is as follows,
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Owner = Application.Current.MainWindow;
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
// Here I need to get the DataContext of PromptWindow's Content
this.PromptWindow.Show();
}
I did some debugging and found that by inherting IContent interface from ModernUI in the 'OnNavigatedTo' event
public void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
{
IPWPMainViewModel pwpMainViewModel = ObjectFactory.GetInstance<IPWPMainViewModel>();
pwpMainViewModel.PromptMainsCollection.Add(new ContentControl { Content = e.Content });
IPromptMainViewModel promptMainViewModel = ((UserControl)e.Content).DataContext as IPromptMainViewModel;
}
Here I am able to get the DataContext of the ModernWindow's Content i.e. of type 'IPromptMainViewModel' but here its very difficult to map/load the views into this ModernWindow as there are multiple instances of views, but I would like to do it in the ViewModel where 'ShowPrompt()' is present as there the Model will be associated with the View correctly so I can map there the views easily.
Thank you.
To get this done, I set the Content of the ModernWindow by myself (as shown in below code in a method in a ViewModel) without using the ContentSource DependencyProperty, If we use the ContentSource property it will be set for a ModernFrame type by the ModernWindow itself creating its Content instance after Navigation to that View completes in some method in ModernFrame class from ModernUI for WPF by using ModernFrame's Source DependencyProperty.
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Title = string.Concat("Control ", this.PromptOriginsEntity.PromptOriginsIdentity);
this.PromptWindow.Tag = this.PromptOriginsEntity.PromptOriginsIdentity;
this.PromptWindow.Owner = Application.Current.MainWindow;
// Store Window object in PromptWindowsCollection
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
this.PromptWindow.Show(); // inorder to retrieve the ModernFrame the ModernWindow is to be shown first
ModernFrame frameContent = (ModernFrame)this.PromptWindow.Template.FindName("ContentFrame", this.PromptWindow);
UserControl userControl = new UserControl { Content = GetView<IPromptViewModel>(), Tag = this.PromptOriginsEntity.PromptOriginsIdentity };
frameContent.Content = userControl;
this.PWPMainViewModel.PromptsCollection.Add(userControl);
IPromptViewModel promptViewModel = (IPromptViewModel)((IView)userControl.Content).DataContext;
promptViewModel.PromptEntity.Identity = this.PromptOriginsEntity.PromptOriginsIdentity;
}
I've uploaded a prototype app at https://wpfmvvmsamples.codeplex.com/SourceControl/latest
Thanks.
In my XAML I have this:
<TextBlock Text="{Binding Stats.Scores.Team}" Style="{StaticResource Column_Value_Large}" />
I need to be able to create that TextBlock, in it's entirety, in the codebehind. Here's what I have:
foreach (var Stats in player){
var columnHeaderScore = new TextBlock
{
Style = Application.Current.Resources["Column_Value_Large"] as Style,
};
columnHeaderScore.SetBinding(TextBlock.TextProperty, new Binding
{
Path = new PropertyPath("Stats.Scores.Team"),
});
columnHeaderStackPanel.Children.Add(columnHeaderScore);
}
However, the binding doesn't seem to be working. What's the appropriate way to set the binding in the codebehind?
Edit for context: my goal is to generate a bunch of these text boxes inside a big loop in the codebehind. See my revised example above which now shows the loop. Since I want to do it this way, I don't think there's any possible way for me to do it in the XAML; I would have to set the binding in the codebehind.
I think you use xaml in a wrong way. Why dont you set theTextBlockin the XAML code and bind itsVisibilityto a property or use aStyle. Then you dont have to create the binding in the codebehind.
EDIT: Why don't you use a ItemPanel or something like that to which you bind your collection and give it a DataTemplate which displays the TextBoxes?
I got it.
My problem was using "Path" in SetBinding instead of "Source". The working code looks like this:
foreach (var Stats in player){
var columnHeaderScore = new TextBlock
{
Style = Application.Current.Resources["Column_Value_Large"] as Style,
};
columnHeaderScore.SetBinding(TextBlock.TextProperty, new Binding
{
Source = Stats.Scores.Team,
});
columnHeaderStackPanel.Children.Add(columnHeaderScore);
}