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;
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 am trying to display a list of filenames of uploaded files using a listbox. I have buttons to upload, open and delete files in the list box. I am using the mvvm pattern and have bound the itemsource to an observable collection. When ever I modify the collection (i.e: upload or del) the listbox doesnt refresh to reflect changes when collection is changed. I am certain that the way I have implemented this is bad. At this point I am totally confused as to how a observable collection notifies changes. Below is my code:
<ListBox x:Name="Listbox1"
SelectedItem="{Binding SelectedFile}"
SelectedIndex="{Binding SelectedIndexNum}"
ItemsSource="{Binding Path=FileNames, UpdateSourceTrigger=PropertyChanged}">
</ListBox>
<Button Content="UploadFile"
Command="{Binding Path=UploadFileCommand}"
</Button>
<Button Content="Delete File"
Command="{Binding Path=DeleteFileCommand}"
</Button>
The view model property:
public ObservableCollection<string> FileNames
{
get
{
if (this.SomeDataStruc.UploadedFileDataCollection == null || this.SomeDataStruc.UploadedFileDataCollection .Count() <= 0)
{
return new ObservableCollection<string>();
}
else
{
var uploadedFileList = this.SomeDataStruc.UploadedFileDataCollection .Where(r => r.Id == this.SomeDataStruc.Id);
var filenames = new ObservableCollection<string>(uploadedFileList.Select(c => c.FileName));//.ToList();
return filenames;
}
}
}
Please note that the SomeDataStruc has an observable collection. this UploadedFileData has many fields out of which I am displaying only the filename in the listbox.
private void DeleteReport(object parameter)
{
this.UploadedFileDataCollection[_selectedIndex].Status = DataStatusEnum.Deleted;
this.SomeDataStruc.UploadedFileDataCollection.RemoveAt(_selectedIndex);
this.OnPropertyChanged("FileNames"); // i need to do this...Listbox doesnt update without this.
}
WHat am i doing wrong. should i be handling collectionchanged event? if so why? isnt that the point of observable collection....that it notifies on its own?
I have checked many similar topics. but that doesnt seem to clear my confusion. Please help me out.
Your problem is that the FileNames property is always returning a new ObservableCollection.
If you had code like this:
private ObservableCollection<string> _fileNames;
public ObservableCollection<string> FileNames
{
get
{
if(_fileNames == null)
{
_fileNames = new ObservableCollection<string>();
}
return _fileNames;
}
}
Then any changes to the FileNames collection would cause notifications and your UI to be updated.
e.g.
private void LoadFileNames()
{
FileNames.Clear();
foreach( ... )
{
FileNames.Add(...); // collection changed notification here
}
}
Since you are changing the collection every time FileNames is used (which is inefficient) you need to call this.OnPropertyChanged("FileNames"); to tell the UI that the whole collection has been replaced.
this is the right behavior, since you dont remove anything from FileNames,just from UploadedFileDataCollection which is a regular list. so clearly you need to raise propertyChanged to teel the UI to recreate FileNames list based on the remained items in UploadedFileDataCollection
I'm using C# WPF MVVM. So in XAML there is a listview, which is binded to an object, used to show different information from sql database depending on tab.
For example. I have two forms: one is that shows information and another that is used to input information. How can I automatically update the listview in one form, after new information was entered in another form? Because now I have to switch tabs to get the listview updated.
binding direction for this element should be exposed to TwoWay (Mode=TwoWay)
like this:
x:Name="list"
ItemsSource="{Binding ....... , Path=........., Mode=TwoWay}}" ......
Apart from the default binding, which is one way, you can also configure binding to be two way, one way to source, and so forth. This is done by specifying the Mode property.
OneWay: Causes changes to the source property to automatically update the target property but the source does not get changed
TwoWay: Changes in the source or target automatically cause updates to the other
OneWayToSource: Causes changes to the target property to automatically update the source property but the target does not get changed
OneTime: Causes only the first time change to the source property to automatically update the target property but the source does not get changed and subsequent changes do not affect the target property
you can look this : http://msdn.microsoft.com/en-us/library/ms752347.aspx
After you enter new information into a form, try to invoke your own method, which will update your information into a list view.
So you can use some event eg. DataContentChanged or your update method can be called when u click the button which adds new data into your form.
Example of refresh method should look like this:
public void lbRefresh()
{
//create itemsList for listbox
ArrayList itemsList = new ArrayList();
//count how many information you wana to add
//here I count how many columns I have in dataGrid1
int count = dataGrid1.Columns.Count;
//for cycle to add my strings of columns headers into an itemsList
for (int i = 0; i < count; i++)
{
itemsList.Add(dataGrid1.Columns[i].Header.ToString());
}
//simply refresh my itemsList into my listBox1
listBox1.ItemsSource = itemsList;
}
EDIT: To finish and solve your problem, try to use this snippet of code:
//some btn_Click Event in one window
//(lets say, its your callback " to update" button in datagrid)
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//here you doing somethin
//after your datagrid got updated, try to store the object,
//which u want to send into your eg. listbox
data[0] = data; //my stored data in array
//for better understanding, this method "Button_Click_1" is called from Window1.xaml.cs
//and I want to pass information into my another window Graph1.xaml.cs
//create "newWindow" object onto your another window and send "data" by constuctor
var newWindow = new Graph1(data); //line *
//you can call this if u want to show that window after changes applied
newWindow.Show();
}
After that your Graph1.xaml.cs should look like this:
public partial class Graph1 : Window
{//this method takes over your data u sent by line * into previous method explained
public Graph1(int[]data)
{
InitializeComponent();
//now you can direcly use your "data" or can call another method and pass your data into it
ownListBoxUpdateMethod(data);
}
private void ownListBoxUpdateMethod(int[] data)
{
//update your listbox here and its done ;-)
}
I've started to learn about GridView (XAML) for Windows 8 Store apps. I've used the ItemsPage example to try and have a look at the code.
My aim is use it as a horizontal menu, on the click event it will simply open another xaml page.
I've edited the SampleDataSource.cs file and filled up with the wanted content, i thought that there would be an easier, cleaner way of doing this. This Menu won't be dynamic and won't change so i'm looking for a way to statically add the menu items.
I can do the following to add a simple text entry;
<x:String>Item 1</x:String>
But i have i'm not sure how to bind to certain parts, such as the image and title elements
Right, i think i'm understanding this a little bit more. The following code would try and bind to itemsViewSource
<CollectionViewSource
x:Name="itemsViewSource"
Source="{Binding Items}"
d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
So would i need to create a new list, such as;
List<String> itemsList = new List<string>();
itemsList.Add("Item 1");
itemsList.Add("Item 2");
Only issue is that each item (line) would need a Title and Image text field and how to bind those to the correct items.
If your ItemsControl (GridView) needs to display more than one bound item for each list item - you need to bind its Source to a list of something more than a string unless you want to parse the strings. In your case you would have something like
List<TitleAndImageText> itemsList = new List<TitleAndImageText>();
itemsList.Add(new TitleAndImageText { Title = "Title 1", ImageText = "Image Text 1");
itemsList.Add(new TitleAndImageText { Title = "Title 2", ImageText = "Image Text 2");
Then in your ItemTemplate/DataTemplate you would have two TextBlocks - that bind to the properties of your item view model like this: Text="{Binding Title}", Text="{Binding ImageText}".
Of course you need to define your TitleAndImageText, e.g.
public class TitleAndImageText
{
public string Title { get; set; }
public string ImageText { get; set; }
}
I have the same problem like this. But I´m using a DataGrid instead of a ListBox and it does not seem to work like this (it might also be because i never used visual basic and didnt translate the code correcly into c#).
I basicly want two DataGrids on the same data with different filters.
ICollectionView view_dataLinesUnfiltered;
ICollectionView view_dataLinesFiltered;
public MainWindow()
{
...
//view_dataLines = CollectionViewSource.GetDefaultView(dataLines); // <- Filter works on both
view_dataLinesUnfiltered = new CollectionView(dataLines); // <- Filter doesn´t work at all
view_dataLinesFiltered = new CollectionView(dataLines);
....
// Control Events
this.ShowAA.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ToggleButton.UncheckedEvent));
}
private void ShowAA_Checked(object sender, RoutedEventArgs e)
{
view_dataLinesUnfiltered.Filter = null;
}
private void ShowAA_UnChecked(object sender, RoutedEventArgs e)
{
view_dataLinesUnfiltered.Filter = delegate(object o) { return FilterContent(o as ErrorDetection.stDataLine, "AA", ""); };
}
bool FilterContent(ErrorDetection.stDataLine line, string sFilterAA, string sFilter)
{
shortArrayToHexStringConverter converter = new shortArrayToHexStringConverter();
string comBuffer = converter.Convert(line.ComBufferP as object,typeof(string),0,System.Globalization.CultureInfo.CurrentCulture) as string;
return false;// !comBuffer.Contains("AA");
}
The FilterContent method is being called without problems, but the DataGrid shows the lines anyway. If I use GetDefaultView the Filter works on both Datagrids. Do I have to use some other view instead of CollectionView (ListCollectionView does also not work)?
i have made a small sample project to show the problem sample. It only consists of an constructor and an observable collection.
I got it to work somehow. I used CollectionViewSources now instead of ICollectionView.
<Window.Resources>
<CollectionViewSource x:Key="viewSource_dataLinesUnfiltered"/>
<CollectionViewSource x:Key="viewSource_dataLinesFiltered"/>
</Window.Resources>
...
<DataGrid Name="Filtered_Datagrid" ItemsSource="{Binding Source={StaticResource viewSource_dataLinesFiltered}}" >
...
</DataGrid>
...
<DataGrid Name="Unfiltered_Datagrid" ItemsSource="{Binding Source={StaticResource viewSource_dataLinesUnfiltered}}">
...
</DataGrid>
and the c Code:
CollectionViewSource viewSource_dataLinesUnfiltered;
CollectionViewSource viewSource_dataLinesFiltered;
...
public MainWindow()
{
InitializeComponent();
this.DataContext = dataLines;
viewSource_dataLinesUnfiltered = (CollectionViewSource)this.Resources["viewSource_dataLinesUnfiltered"];
viewSource_dataLinesUnfiltered.Source = dataLines;
viewSource_dataLinesFiltered = (CollectionViewSource)this.Resources["viewSource_dataLinesFiltered"];
viewSource_dataLinesFiltered.Source = dataLines;
// Control Events
this.ShowAA.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ToggleButton.UncheckedEvent));
}
private void ShowAA_Checked(object sender, RoutedEventArgs e)
{
viewSource_dataLinesUnfiltered.View.Filter = null;
}
private void ShowAA_UnChecked(object sender, RoutedEventArgs e)
{
viewSource_dataLinesUnfiltered.View.Filter = delegate(object o) { return FilterContent(o as ErrorDetection.stDataLine, "AA", ""); };
}
bool FilterContent(ErrorDetection.stDataLine line, string sFilterAA, string sFilter)
{
shortArrayToHexStringConverter converter = new shortArrayToHexStringConverter();
string comBuffer = converter.Convert(line.ComBufferP as object,typeof(string),0,System.Globalization.CultureInfo.CurrentCulture) as string;
return !comBuffer.Contains("AA");
}
But I´m not sure why it works this way and the filter is not applied on window repaints when ICollectionView is used.
You need to specify which ICollectionVIew is used on which DataGrid.
If you just bind to the collection (dataLines in this case) WPF will use the 'default view' (or create one if necessary), this is why the first commented out line works for filtering.
There are a few ways you could specify which view is used for which datagrid, depending on what patterns, etc. you are using
1) Like the linked question, you could set the ItemsSource for each DataGrid in the window's code behind, after initializing the views, e.g.:
filteredDataGrid.ItemsSource = view_dataLinesFiltered;
unfilteredDataGrid.ItemsSource = view_dataLinesUnfiltered;
2) You could set the DataContext of the window to itself, or make a view model for the screen that contains the view, and make the views public properties and then bind to the intended view for each grid, e.g.
<DataGrid ItemsSource="{Binding View_dataLinesFiltered}"> ....
Edit:
Now I'm not at work and can get to dropbox and play with your example it seems like the cause of the weird behaviour is the use of CollectionView directly. On the msdn page for CollectionView it says
You should not create objects of this class in your code. To create a
collection view for a collection that only implements IEnumerable,
create a CollectionViewSource object, add your collection to the
Source property, and get the collection view from the View property.
However, if you don't want to set up the views in XAML, you could also change your CollectionViews to ListCollectionViews and it should work as expected (this is likely the view type that CollectionViewSource is making for you behind the scenes anyway).