I opened the question here but we cannot come to the solution for my problem. I decided to create new question as we came to some assumptions and the former question does not refer to the real problem(we thought it is the problem with binding but as you will read on it is not).
In few words I have a ListView with data from list called jointList.
The list is doing well and it has all the data necessary. (I checked it)
On each row of the ListView I put a ToggleSwitch(in xaml) and then I try to do something with each of the switches.
Each switch should correspond to the data from the same row.
I created Toggled event that should apply to all toggleSwitches like this:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
foreach (var product in jointList)
{
if (product.IsOn == true)
{
ToggleTest.Text = product.ProductId.ToString(); // this is for testing only, later I would do something with the data retrieved
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
But this is making only one toggleSwitch work. It's the switch that corresponds to the last added product to the list ( I am guessing that it is refering to the last Id). The other switches return nothing as if the method was not iterating through the list correctly or as if there was only one switch hooked up.
So, is it possible to get all switches up and running by using just one Toggled event as I attempt to do?
Here's a sample which shows one way.
In this example we have the following Product view model:
public class Product : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
So just a single Name-property.
Then we have MainPage where we create a collection of products:
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var items = new ObservableCollection<Product>();
for (int i = 0; i < 9; i++)
{
items.Add(new Product($"item {i}"));
}
this.Items.ItemsSource = items;
}
And the XAML which creates the view:
<ListView Loaded="FrameworkElement_OnLoaded" x:Name="Items">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="RowContent" Text="{Binding Name}"/>
<ToggleSwitch x:Name="Toggle" Grid.Column="1" Toggled="Toggle_OnToggled"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The result:
Now we want to change the text when user toggles the switch. This is done in Toggle_OnToggled-event handler:
private void Toggle_OnToggled(object sender, RoutedEventArgs e)
{
var toggle = (ToggleSwitch) sender;
var dataContext = ((Grid)toggle.Parent).DataContext;
var dataItem = (Product) dataContext;
dataItem.Name = $"Toggled {toggle.IsOn}";
}
So after a few toggles:
Mikael Koskinen has delivered the answer to my problem.
Most of my code was correct and identical to his solution, apart from the last bit that is OnToggled event handler.
Here is the working andd correct handler:
private void Toggle_OnToggled(object sender, RoutedEventArgs e)
{
var toggle = (ToggleSwitch)sender;
var dataContext = ((Grid)toggle.Parent).DataContext;
var dataItem = (ScheduleList)dataContext;
ToggleTest.Text = dataItem.ProductId;
}
My previous version of handler didn't include the important bit, that is dataContext and dataItem.
It works like a charm now.
I'm developing a project in c# Wpf with mvvm light.
In this project I have a datagrid with SelectedIndex bound to an int in the ViewModel.
DocumentViewModel:
private int _docSelectedIndex;
public int DocSelectedIndex
{
get { return _docSelectedIndex; }
set
{
_docSelectedIndex = value;
RaisePropertyChanged("DocSelectedIndex");
}
}
The View:
<DataGrid HeadersVisibility="Column"
x:Name="docgrid"
IsSynchronizedWithCurrentItem="True"
DataContext ="{Binding Document, Source={StaticResource Locator}}"
ItemsSource="{Binding Path=DocItems}"
SelectedIndex="{Binding DocSelectedIndex,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
...
...
</DataGrid>
The binding works! but whenever I want to update the Grid with new data I need to RaisePropertyChanged("DocItems").
This seems to change the selectedindex to 0. I've tried to set the selectedindex back to the original, but the index is always set to 0 in the end.
This is the method that calls RaisePropertyChanged:
public void UpdateDocumentList(object sender, TypedEventArg<DocListUpdatedEvent> e)
{
var temp = new List<SFODocument>(e.Value.DocumentList);
var meta = _meta.GetPageMetaData();
foreach (var d in temp)
{
foreach (int i in d.PageList)
{
meta[i].docid = d.DocumentID;
_meta.UpdateExistingMeta(meta[i]);
}
}
_docItems = new ObservableCollection<SFODocument>(temp);
RaisePropertyChanged("DocItems");
}
How can I update the datagrid and still keep the original selectedIndex ?
Try to set the selectedindex back to the original this way
Dispatcher.BeginInvoke(new Action(() => docgrid.SelectedIndex = oldIndex), DispatcherPriority.DataBind);
If it won't work try to use another priorities.
In a past few months I've played a lot with the TreeView and now I get to the UI freeze problem. It comes when you have large amount of the items and the data part for those Items are created very quickly but creating TreeViewItems and visualizing those (it must be done on UI thread) takes a time.
Let's take Shell browser and C:\Windows\System32 directory as an example. (I reworked http://www.codeproject.com/Articles/24237/A-Multi-Threaded-WPF-TreeView-Explorer solution for that.) This directory has ~2500 files and folders.
The DataItem and Visual loading are implemented in different threads but as the file and directory info are read quickly it gives no benefit. Application freezes when it creates TreeViewItems and makes those visible.
I've tried:
Set a different DispatcherPriorities for the UI thread when to load items, for example the window was interactive (I was able to move it) with DispatcherPriority.ContextIdle, but then items were loaded really slow..
Create and visualize items in blocks, like 100 items per once, but hat no benefit, the UI thread still were freezing..
My goal is that the application would be interactive while loading those item's!
At the moment I have only one idea how to solve this, to implement my own control which tracks window size, scrollbar position and loads only the items which are visable, but it's not so easy to do that and I'm not sure that at the end performance would be better.. :)
Maybe somebody has idea how to make application interactive while loading bunch of visual items?!
Code:
Complete Solution could be found there: http://www.speedyshare.com/hksN6/ShellBrowser.zip
Program:
public partial class DemoWindow
{
public DemoWindow()
{
InitializeComponent();
this.Loaded += DemoWindow_Loaded;
}
private readonly object _dummyNode = null;
delegate void LoaderDelegate(TreeViewItem tviLoad, string strPath, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem);
delegate void AddSubItemDelegate(TreeViewItem tviParent, IEnumerable<ItemToAdd> itemsToAdd);
// Gets an IEnumerable for the items to load, in this sample it's either "GetFolders" or "GetDrives"
// RUNS ON: Background Thread
delegate IEnumerable<ItemToAdd> DEL_GetItems(string strParent);
void DemoWindow_Loaded(object sender, RoutedEventArgs e)
{
var tviRoot = new TreeViewItem();
tviRoot.Header = "My Computer";
tviRoot.Items.Add(_dummyNode);
tviRoot.Expanded += OnRootExpanded;
tviRoot.Collapsed += OnItemCollapsed;
TreeViewItemProps.SetItemImageName(tviRoot, #"Images/Computer.png");
foldersTree.Items.Add(tviRoot);
}
void OnRootExpanded(object sender, RoutedEventArgs e)
{
var treeViewItem = e.OriginalSource as TreeViewItem;
StartItemLoading(treeViewItem, GetDrives, AddItem);
}
void OnItemCollapsed(object sender, RoutedEventArgs e)
{
var treeViewItem = e.OriginalSource as TreeViewItem;
if (treeViewItem != null)
{
treeViewItem.Items.Clear();
treeViewItem.Items.Add(_dummyNode);
}
}
void OnFolderExpanded(object sender, RoutedEventArgs e)
{
var tviSender = e.OriginalSource as TreeViewItem;
e.Handled = true;
StartItemLoading(tviSender, GetFilesAndFolders, AddItem);
}
void StartItemLoading(TreeViewItem tviSender, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem)
{
tviSender.Items.Clear();
LoaderDelegate actLoad = LoadSubItems;
actLoad.BeginInvoke(tviSender, tviSender.Tag as string, actGetItems, actAddSubItem, ProcessAsyncCallback, actLoad);
}
void LoadSubItems(TreeViewItem tviParent, string strPath, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem)
{
var itemsList = actGetItems(strPath).ToList();
Dispatcher.BeginInvoke(DispatcherPriority.Normal, actAddSubItem, tviParent, itemsList);
}
// Runs on Background thread.
IEnumerable<ItemToAdd> GetFilesAndFolders(string strParent)
{
var list = Directory.GetDirectories(strParent).Select(itemName => new ItemToAdd() {Path = itemName, TypeOfTheItem = ItemType.Directory}).ToList();
list.AddRange(Directory.GetFiles(strParent).Select(itemName => new ItemToAdd() {Path = itemName, TypeOfTheItem = ItemType.File}));
return list;
}
// Runs on Background thread.
IEnumerable<ItemToAdd> GetDrives(string strParent)
{
return (Directory.GetLogicalDrives().Select(x => new ItemToAdd(){Path = x, TypeOfTheItem = ItemType.DiscDrive}));
}
void AddItem(TreeViewItem tviParent, IEnumerable<ItemToAdd> itemsToAdd)
{
string imgPath = "";
foreach (ItemToAdd itemToAdd in itemsToAdd)
{
switch (itemToAdd.TypeOfTheItem)
{
case ItemType.File:
imgPath = #"Images/File.png";
break;
case ItemType.Directory:
imgPath = #"Images/Folder.png";
break;
case ItemType.DiscDrive:
imgPath = #"Images/DiskDrive.png";
break;
}
if (itemToAdd.TypeOfTheItem == ItemType.Directory || itemToAdd.TypeOfTheItem == ItemType.File)
IntAddItem(tviParent, System.IO.Path.GetFileName(itemToAdd.Path), itemToAdd.Path, imgPath);
else
IntAddItem(tviParent, itemToAdd.Path, itemToAdd.Path, imgPath);
}
}
private void IntAddItem(TreeViewItem tviParent, string strName, string strTag, string strImageName)
{
var tviSubItem = new TreeViewItem();
tviSubItem.Header = strName;
tviSubItem.Tag = strTag;
tviSubItem.Items.Add(_dummyNode);
tviSubItem.Expanded += OnFolderExpanded;
tviSubItem.Collapsed += OnItemCollapsed;
TreeViewItemProps.SetItemImageName(tviSubItem, strImageName);
tviParent.Items.Add(tviSubItem);
}
private void ProcessAsyncCallback(IAsyncResult iAR)
{
// Call end invoke on UI thread to process any exceptions, etc.
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() => ProcessEndInvoke(iAR)));
}
private void ProcessEndInvoke(IAsyncResult iAR)
{
try
{
var actInvoked = (LoaderDelegate)iAR.AsyncState;
actInvoked.EndInvoke(iAR);
}
catch (Exception ex)
{
// Probably should check for useful inner exceptions
MessageBox.Show(string.Format("Error in ProcessEndInvoke\r\nException: {0}", ex.Message));
}
}
private struct ItemToAdd
{
public string Path;
public ItemType TypeOfTheItem;
}
private enum ItemType
{
File,
Directory,
DiscDrive
}
}
public static class TreeViewItemProps
{
public static string GetItemImageName(DependencyObject obj)
{
return (string)obj.GetValue(ItemImageNameProperty);
}
public static void SetItemImageName(DependencyObject obj, string value)
{
obj.SetValue(ItemImageNameProperty, value);
}
public static readonly DependencyProperty ItemImageNameProperty;
static TreeViewItemProps()
{
ItemImageNameProperty = DependencyProperty.RegisterAttached("ItemImageName", typeof(string), typeof(TreeViewItemProps), new UIPropertyMetadata(string.Empty));
}
}
Xaml:
<Window x:Class="ThreadedWpfExplorer.DemoWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ThreadedWpfExplorer"
Title="Threaded WPF Explorer" Height="840" Width="350" Icon="/ThreadedWpfExplorer;component/Images/Computer.png">
<Grid>
<TreeView x:Name="foldersTree">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate DataType="ContentPresenter">
<Grid>
<StackPanel Name="spImg" Orientation="Horizontal">
<Image Name="img"
Source="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type TreeViewItem}},
Path=(local:TreeViewItemProps.ItemImageName)}"
Width="20" Height="20" Stretch="Fill" VerticalAlignment="Center" />
<TextBlock Text="{Binding}" Margin="5,0" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
Alternative Loading items in blocks:
private const int rangeToAdd = 100;
void LoadSubItems(TreeViewItem tviParent, string strPath, DEL_GetItems actGetItems, AddSubItemDelegate actAddSubItem)
{
var itemsList = actGetItems(strPath).ToList();
int index;
for (index = 0; (index + rangeToAdd) <= itemsList.Count && rangeToAdd <= itemsList.Count; index = index + rangeToAdd)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, actAddSubItem, tviParent, itemsList.GetRange(index, rangeToAdd));
}
if (itemsList.Count < (index + rangeToAdd) || rangeToAdd > itemsList.Count)
{
var itemsLeftToAdd = itemsList.Count % rangeToAdd;
Dispatcher.BeginInvoke(DispatcherPriority.Normal, actAddSubItem, tviParent, itemsList.GetRange((rangeToAdd > itemsList.Count) ? index : index - rangeToAdd, itemsLeftToAdd));
}
}
What you're looking for is known as UI Virtualization and is supported by a number of different WPF controls. Regarding the TreeView in particular, see this article for details on how to turn on virtualization.
One major caveat is that in order to benefit from this feature, you need to use the ItemsSource property and provide items from a collection rather than adding items directly from your code. This is a good idea to do anyway, but it may require some restructuring to get it functional with your existing code.
Why not just create your observable collection and bind to it from xaml?
Check out the MvvM design pattern and you just create a class, and point the xaml at it, in there, from the initialisation, create your list, and then tell the treeview to bind to that list, displaying properties of the each item in your list.
I know this is a little scant on info, but to do MvvM is really easy and just look through stackoverflow and you'll see examples.
You really don't need to call begininvoke on every item - and that's just not from an mvvm point of view - just bind to a list.
You can use indexed 'levels' to your objects too.
Another helpful technique is this regard, is Data Virtualization. There is a good article and sample project on CodeProject, that talks about Data Virtualization in WPF.
I have a UserControl that is comprised of a few bound ItemsControl's and strings, and based on the button that is pressed, different data is displayed. Here is an example of one of the Button's click events:
private void LeftPreviousScoresButton_Click(object sender, RoutedEventArgs e)
{
if (m_previousScoresWindow.Visibility == Visibility.Visible)
{
m_previousScoresWindow.Hide();
}
else
{
WindowTitle = "Left Side";
PreviousScoresA = m_previousLeftWristErosionScoresReaderA;
PreviousScoresB = m_previousLeftWristErosionScoresReaderB;
m_previousScoresWindow.Show();
}
}
There are several of these click event listeners which assigns WindowTitle, PreviousScoresA, and PreviousScoresB with the associated data. The UserControl then binds to them like this:
<ItemsControl Height="Auto" Width="Auto"
ItemsSource="{Binding ElementName=ParentForm, Path=PreviousScoresA}"
Grid.Row="1" />
<ItemsControl Height="Auto" Width="Auto"
ItemsSource="{Binding ElementName=ParentForm, Path=PreviousScoresB}"
Grid.Row="2" />
<TextBlock FontSize="16" FontWeight="Bold" Height="25"
Margin="5" HorizontalAlignment="Center" Foreground="Black"
Text="{Binding ElementName=ParentForm, Path=PreviousScoresWindowTitle}" />
However, when opening the window, the old data displays for a second before it is updated with the current data. I've even tried adding these calls when calling Hide() on the Window but it didn't seem to help:
WindowTitle = String.Empty;
PreviousScoresA = new ObservableCollection<PreviousScoreData>();
PreviousScoresB = new ObservableCollection<PreviousScoreData>();
Is there any way to ensure that Show() is not called until after the bound data has been updated? Thanks.
As it appears you are using an ObservableCollection, the collection should never be re-initialized. Rather, it should just be cleared and then add the new values; this is what helps keep the collection synchronized when using an ObservableCollection.
This is a bit of a shot in the dark based on your code sample; if you clear the collection when hiding and then refill them with the new values, then you should get the desired effect:
private void LeftPreviousScoresButton_Click(object sender, RoutedEventArgs e)
{
if (m_previousScoresWindow.Visibility == Visibility.Visible)
{
m_previousScoresWindow.Hide();
WindowTitle = string.Empty;
PreviousScoresA.Clear();
PreviousScoresB.Clear();
}
else
{
WindowTitle = "Left Side";
// do not re-initialize the collection; clear and add new values
// PreviousScoresA = m_previousLeftWristErosionScoresReaderA;
// PreviousScoresB = m_previousLeftWristErosionScoresReaderB;
ReFillScores(PreviousScoresA, m_previousLeftWristErosionScoresReaderA);
ReFillScores(PreviousScoresB, m_previousLeftWristErosionScoresReaderB);
m_previousScoresWindow.Show();
}
}
private void ReFillScores (ObservableCollection<PreviousScoreData> collection, IEnumerable<PreviousScoreData> values)
{
collection.Clear();
foreach(PreviousScoreData d in values)
{
collection.Add(d);
}
}
I want a listbox that will show all the images and text "layers" that I have on my Canvas in silverlight. The code I have currently crashes when I try to view the listbox or when I'm viewing the listbox when I add an element. I can't figure out why. Can someone point me in the right direction with this?
XML -
<Grid DataContext="{Binding Path=Project}">
...
...
<TextBlock Name="textBlock1" Text="Layers" Margin="18,16,0,0" />
<StackPanel Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<ListBox ItemsSource="{Binding Path=Elements}" Height="175" Name="listBox1" Width="172"/>
</StackPanel>
</Grid>
Project.cs
//List of elements
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
elements = value;
NotifyPropertyChanged("Elements");
}
}
// An example of how an element is added to the Elements collection
// There are also image elements added similarly
private void AddTextElement(object param)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = "New Text";
textBlock.Foreground = new SolidColorBrush(Colors.Gray);
textBlock.FontSize = 25;
textBlock.FontFamily = new FontFamily("Arial");
textBlock.Cursor = Cursors.Hand;
textBlock.Tag = null;
this.Elements.Add(textBlock);
numberOfElements++;
this.SelectedElement = textBlock;
this.selectedTextElement = textBlock;
}
private void AddImageElement(object param)
{
bool? gotImage;
string fileName;
BitmapImage imageSource = GetImageFromLocalMachine(out gotImage, out fileName);
if (gotImage == true)
{
Image image = new Image();
OrderElements(image);
image.Name = fileName;
image.Source = imageSource;
image.Height = imageSource.PixelHeight;
image.Width = imageSource.PixelWidth;
image.MaxHeight = imageSource.PixelHeight;
image.MaxWidth = imageSource.PixelWidth;
image.Cursor = Cursors.Hand;
image.Tag = null;
AddDraggingBehavior(image);
image.MouseLeftButtonUp += element_MouseLeftButtonUp;
this.Elements.Add(image);
numberOfElements++;
this.SelectedElement = image;
this.SelectedImageElement = image;
}
}
One reason might be, because you bind using Path property in your Grid element.
You should use binding source, and set your Project object as a staticresource which you can point to when you call binding source.
Like this:
<Window
xlmns:local="NamespaceOfMyProject">
<Window.Resources>
<local:Project x:key="MyProjectResource" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource MyProjectResource}}>
....
</Grid>
....
</Window>
Reason is: You use "Source" when you point to objects, and "Path" when you point to properties.
Another way to set the DataContext is to do it in the codebehind, using this C# code. But first give your grid a name, so it can be referenced in the codebehind:
<Grid x:Name="myGrid">
Codebehind:
myGrid.DataContext = new Project();
Working with Images incorrectly will typically cause the crash; Show the code for implementation of your Elements and how you setting the images.
Also your XAML is missing ItemTemplate,where u would set the image and text.
I'd guess it's crashing because you've got FrameworkElements that you've added to a Canvas, but then you're also adding them to your List. FrameworkElements generally don't like being added to the visual tree multiple times.
If this is the problem, something like this might work around it (bind your list to ElementsAsStrings):
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
if(elements != null)
elements.CollectionChanged -= onElementsChanged;
elements = value;
if(elements != null)
elements.CollectionChanged += onElementsChanged;
NotifyPropertyChanged("Elements");
NotifyPropertyChanged("ElementsAsStrings");
}
}
public IEnumerable<string> ElementsAsStrings
{
get
{
foreach(var element in Elements)
{
if(element is TextBox)
yield return (element as TextBox).Text;
// More cases here
}
}
}
private void onElementsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("ElementsAsStrings");
}