ListBox not refreshing using mvvm - c#

I have asked how to bind listbox to mvvm in this thread:
Listbox is not Populating using binding
I created another thread to ask why my ObservableList is not Refreshing.
The List is now populated on Load
but not populating or refreshing after I select the Folder
The program is supposed to Populate the list after I select the program
this is the structure of my folders and class
this is the code for my FolderBrowserDialogVM
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Bates_Writer.ViewModel
{
public class FolderBrowserDialogVM :ViewModelBase
{
public System.Collections.ObjectModel.ObservableCollection<string> FileNames { get; }
= new System.Collections.ObjectModel.ObservableCollection<string>()
{
"Wayne",
"Cinderella",
"Malificient"
};
public static RelayCommand OpenCommand { get; set; }
private string _selectedPath;
public string SelectedPath
{
get { return _selectedPath; }
set
{
_selectedPath = value;
RaisePropertyChanged("SelectedPath");
}
}
private string _defaultPath;
public FolderBrowserDialogVM()
{
RegisterCommands();
}
public FolderBrowserDialogVM(string defaultPath)
{
_defaultPath = defaultPath;
RegisterCommands();
}
private void RegisterCommands()
{
OpenCommand = new RelayCommand(ExecuteOpenFileDialog);
}
private void ExecuteOpenFileDialog()
{
var dialog = new FolderBrowserDialog();
dialog.ShowDialog();
SelectedPath = dialog.SelectedPath;
FileNames.Clear();
foreach (var file in System.IO.Directory.EnumerateFiles(SelectedPath, "*", System.IO.SearchOption.AllDirectories))
{
FileNames.Add(file);
Console.WriteLine(file);
}
}
}
}
this is the code for FilesView.xaml(user control)
<UserControl x:Class="Bates_Writer.View.FilesView"
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"
xmlns:local="clr-namespace:Bates_Writer.View"
xmlns:vm="clr-namespace:Bates_Writer.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:FolderBrowserDialogVM>
</vm:FolderBrowserDialogVM>
</UserControl.DataContext>
<Grid>
<ListBox ItemsSource="{Binding FileNames}" Margin="5,5,5,5"/>
</Grid>
</UserControl>
I tried How to: Implement Property Change Notification
but I can't make it work.
this is my FolderBrowserDialogV.xaml(user control)
<UserControl x:Class="Bates_Writer.View.FolderBrowserDialogV"
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"
xmlns:local="clr-namespace:Bates_Writer.View"
xmlns:vm="clr-namespace:Bates_Writer.ViewModel"
mc:Ignorable="d" Height="42.056" Width="679.439">
<UserControl.DataContext>
<vm:FolderBrowserDialogVM>
</vm:FolderBrowserDialogVM>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="127*"/>
<ColumnDefinition Width="506*"/>
<ColumnDefinition Width="110*"/>
</Grid.ColumnDefinitions>
<Label Content="Folder Location: " FontSize="14" VerticalContentAlignment="Center"/>
<TextBox Grid.Column="1" FontSize="14" Margin="5,5,5,5" VerticalContentAlignment="Center" Text="{Binding SelectedPath}"/>
<Button Command="{Binding OpenCommand}" Content="Browse" Grid.Column="2" FontSize="14" Margin="5,5,5,5" Cursor="Hand"/>
</Grid>
</UserControl>
I even uploaded the sample project here

It looks like you have two instances of FolderBrowserDialogVM. One instance is DataContext for your FolderBrowserDialogV(here you have bound the command, but not the ObservableCollection) and another one for your FilesView(here you have bound ObservableCollection, but not the command, so your ObservableCollection will not be changed in this instance). For it works in your case, you should use(bind to) the same instance.
Delete from your UserControls:
<UserControl.DataContext>
<vm:FolderBrowserDialogVM>
</vm:FolderBrowserDialogVM>
</UserControl.DataContext>
Create an instance of your ViewModel in resources of your main window and set this instance as DataContext for your Usercontrols:
<MainWindow
xmlns:vm="clr-namespace:Bates_Writer.ViewModel" .. >
<MainWindow.Resources>
<vm:FolderBrowserDialogVM x:Key="vmInstance"/>
</MainWindow.Resources>
<StackPanel>
<YourUserControl1 DataContext="{StaticResource vmInstance}"/>
<YourUserControl2 DataContext="{StaticResource vmInstance}"/>
</StackPanel>
</MainWindow>

Related

Changing the ContentControl binding results in a null DataContext

Perhaps one of you could enlighten me on the following problem.
I'm using a list box as a way to navigate between 2 viewmodels.
<Window x:Class="MyWPFApp.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="8*"/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" cal:Message.Attach="[Event SelectionChanged]
= [Action onViewSelectionChange($this.SelectedItem.Text)]">
<TextBlock Text="FirstView"/>
<TextBlock Text="SecondView"/>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding MyViewModel}"/>
</Grid>
namespace MyWPFApp
{
public class ShellViewModel : Caliburn.Micro.PropertyChangedBase, IShell
{
private object _vm;
public object MyViewModel
{
get { return _vm; }
set { _vm = value; NotifyOfPropertyChange(() => MyViewModel); }
}
public FirstViewModel vm1;
public SecondViewModel vm2;
public ShellViewModel()
{
vm1 = new FirstViewModel();
vm2 = new SecondViewModel();
MyViewModel = vm1;
}
public void onViewSelectionChange(string str)
{
switch (str)
{
case "FirstView":
MyViewModel = vm1;
break;
case "SecondView":
MyViewModel = vm2;
break;
default:
break;
}
}
}
}
I've create 2 datatemplates to associate the viewmodel with its view
<Application x:Class="MyWPFApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-MyWPFApp">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<local:AppBootstrapper x:Key="bootstrapper" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type local:FirstViewModel}">
<Grid>
<local:FirstView></local:FirstView>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondViewModel}">
<Grid>
<local:SecondView></local:SecondView>
</Grid>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
FirstView actually has controls which have bindings to properties in FirstViewModel
<UserControl x:Class="MyWPFApp.FirstView"
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"
xmlns:local="clr-namespace:MyWPFApp"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Aquamarine">
<ListBox SelectedIndex="{Binding SelectedIndex}"
SelectedItem="{Binding SelectedItem}"
cal:Message.Attach="[Event SelectionChanged]=[Action OnLBSelectionChanged($source)]">
<ListBoxItem>apple</ListBoxItem>
<ListBoxItem>orange</ListBoxItem>
<ListBoxItem>pear</ListBoxItem>
</ListBox>
</Grid>
using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyWPFApp
{
public class FirstViewModel : PropertyChangedBase
{
private int _selectedindex;
public int SelectedIndex
{
get { return _selectedindex; }
set { _selectedindex = value; NotifyOfPropertyChange(() => SelectedIndex); }
}
private string _selecteditem;
public string SelectedItem
{
get { return _selecteditem; }
set { _selecteditem = value; NotifyOfPropertyChange(() => SelectedItem); }
}
public FirstViewModel()
{
}
public void OnLBSelectionChanged(System.Windows.Controls.ListBox lb)
{
if(lb.DataContext == null)
{
Debug.WriteLine("The DataContext is null");
}
}
}
}
The SecondView contains no controls.
When the ShellView list box selection changes from FirstView to SecondView, I've noticed that the FirstView ListBox DataContext becomes null.
public void OnLBSelectionChanged(System.Windows.Controls.ListBox lb)
{
if(lb.DataContext == null)
{
Debug.WriteLine("The DataContext is null");
}
}
I would prefer not to have to handle selection changes looking for a null DataContext. Instead, I'd like to know what's actually happening.
Thanks.
The reason why this is happening is that the DataContext for FirstView is implicitly wired up by WPF when the ContentControl's Content property is set to the FirstViewModel via data binding.
Once the binding changes to be SecondViewModel the FirstViewModel goes out of scope and thus WPF cleans up after itself, removing FirstViewModel, FirstView, and its implicit DataContext binding.
I hope this helps.

Slow performance when filtering ICollectionView<object>

In a WPF application using MVVM I query a database to get an ObservableCollection of clients, create an ICollectionView and apply a filter function.
On my usercontrol I bind the text used for the filter to a textbox and the ICollectionView to a Listbox.
The ICollectionView initally contains 1104 clients (just ClientID and ClientName).
Retrieving the data from the database is very quick. However, the listbox takes about 4 seconds to populate.
As I enter text into the filter, if the number of clients to return is low then the listbox redraws relatively quickly. However, if I clear out the textbox then it's another 4 seconds to redraw.
Am I missing something, or is my code not very well written.
Thanks for any advice/help.
View:
<UserControl x:Class="ClientReports.Module.SchemeSelection.Views.Clients"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClientReports.Module.SchemeSelection.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True" >
<Grid>
<StackPanel>
<TextBox materialDesign:HintAssist.Hint="Client Search"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding Search, UpdateSourceTrigger=PropertyChanged}"/>
<ListBox ItemsSource="{Binding ClientsFiltered}" DisplayMemberPath="ClientName" />
</StackPanel>
</Grid>
</UserControl>
ViewModel:
using ClientReports.Common.Infrastructure.Models;
using ClientReports.Common.Infrastructure.Services;
using Prism.Mvvm;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Data;
namespace ClientReports.Module.SchemeSelection.ViewModels
{
public class ClientsViewModel : BindableBase
{
private IClientService clientService;
public ClientsViewModel(){ }
public ClientsViewModel(IClientService clientService)
{
this.clientService = clientService;
Clients = new ObservableCollection<Client>();
GetClients().ContinueWith(x => { });
}
public ObservableCollection<Client> Clients { get; }
public ICollectionView ClientsFiltered { get; set; }
private string clientFilter;
public string Search
{
get => clientFilter;
set
{
clientFilter = value;
ClientsFiltered.Refresh();
RaisePropertyChanged("ClientsFiltered");
}
}
private bool Filter(Client client)
{
return Search == null
|| client.ClientName.IndexOf(Search, StringComparison.OrdinalIgnoreCase) != -1;
}
private async Task GetClients()
{
var clients = await clientService.GetAllAsync();
foreach (var client in clients)
{
Clients.Add(client);
}
ClientsFiltered = CollectionViewSource.GetDefaultView(Clients);
ClientsFiltered.Filter = new Predicate<object>(c => Filter(c as Client));
}
}
}
ListBox likely takes 4 seconds to populate because virtualization is not enabled, so WPF has to create 1104 ListBoxItems (and recreate them when the filter is cleared). By default virtualization is enabled for the ListBox but you can occasionally disable it without even realizing it. In your sample, your ListBox is located in the vertical StackPanel, and it is likely the reason for this behavior. You can try to rewrite the XAML in the following way:
<UserControl x:Class="ClientReports.Module.SchemeSelection.Views.Clients"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClientReports.Module.SchemeSelection.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox materialDesign:HintAssist.Hint="Client Search"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding Search, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="0"/>
<ListBox ItemsSource="{Binding ClientsFiltered}"
DisplayMemberPath="ClientName"
Grid.Row="1" />
</Grid>
</UserControl>
If it does not help you can try to set the fixed height for your ListBox and check it again.
If it does not help either, please check Microsoft documentation on virtualization for other possible reasons: https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/optimizing-performance-controls

Passing Page1 Text to Page 2 WPF MVVM

I have been all over the internet looking for a clear answer and cannot find one. I have created Window Forms apps in the past, and decided to give WPF a try. I am hoping I don't have to move back to WinForms, but cannot figure out the simplest tasks.
How do I pass text in Page1's Textbox to Page2's TextBox.
I apologize if this is a duplicate, but I cannot find a clear answer anywhere. Most beginner mvvm tutorials seem to stick to one page apps.
MainWindo.xaml
<Window x:Class="WpfApp3.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"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Frame Source="/Page1.xaml"/>
</Grid>
</Window>
Page1 Xaml:
<Page x:Class="WpfApp3.Page1"
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"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1"
xmlns:vm="clr-namespace:WpfApp3">
<Page.DataContext>
<vm:Page1ViewModel/>
</Page.DataContext>
<Grid>
<StackPanel>
<TextBlock Margin="20" FontSize="36">Welcome Home</TextBlock>
<TextBox x:Name="UserName" Margin="10 0 10 0" Text=""/>
<Button x:Name="Next" Margin="10 10" Content="Next" Click="Next_Click"/>
</StackPanel>
</Grid>
</Page>
Page1 Code-Behind:
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for Page1.xaml
/// </summary>
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
}
private void Next_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(
new Uri("/Page2.xaml", UriKind.Relative));
}
}
}
Page1 ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp3
{
class Page1ViewModel : Notifier
{
private string username;
public string UserName
{
get { return username; }
set
{
username = value;
OnPropertyChanged("UserName");
}
}
}
}
Page2 Xaml:
<Page x:Class="WpfApp3.Page2"
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"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page2"
xmlns:vm="clr-namespace:WpfApp3>
<Page.DataContext>
<vm:Page2ViewModel/>
<Page.Data.Context>
<Grid>
<StackPanel>
<TextBlock Margin="10">Hello there: </TextBlock>
<TextBox x:Name="TextBox_Name" Margin="10" Text="{Binding UserName}"/>
</StackPanel>
</Grid>
</Page>
Page2 Code-behind:
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for Page2.xaml
/// </summary>
public partial class Page2 : Page
{
public Page2()
{
InitializeComponent();
}
}
}
Page2 View Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp3
{
class Page2ViewModel : Notifier
{
private string textbox_name;
public string TextBox_Name
{
get { return textbox_name; }
set
{
textbox_name = value;
OnPropertyChanged("TextBox_Name");
}
}
}
}
first of all you don't need to move back to WinForms: you can avoid MVVM alltogether and use the traditional code-behind coding model you know even with WPF. I'd suggest doing so for your first WPF applications if you don't feel at ease with MVVM.
As for your problem, a solution could be to use static properties for common values storage.
public class MyCommonValues
{
public static string SharedText { get; set; }
}
class Page1ViewModel : Notifier
{
private string username;
public string UserName
{
get { return username; }
set
{
username = value;
OnPropertyChanged("UserName");
OnUserNameChanged(); // added to your code
}
}
// following is an addition to your code
void OnUserNameChanged()
{
MyCommonValues.SharedText = UserName;
}
}
class Page2ViewModel : Notifier
{
// following is an addition to your code
public Page2ViewModel()
{
TextBox_Name = MyCommonValues.SharedText;
}
private string textbox_name;
public string TextBox_Name
{
get { return textbox_name; }
set
{
textbox_name = value;
OnPropertyChanged("TextBox_Name");
}
}
}

WPF/C# Assigning a ViewModel to a custom control from parent view

I'm very new to C# and WPF, and I'm struggling a bit to get data where I need it.
I have one master set of data, which needs to be shared with various user controls, each of which have their own ViewModel. The problem is that I don't seem to be able to assign a ViewModel to a control from the parent XAML and then access that ViewModel from within the custom control's XAML.
I bind the control to a Viewmodel, but then the datacontext within the control doesn't allow me to access that model within the xaml, or I can set the datacontext in the user control so I can access its viewmodel, but then I can't bind to the viewmodel in xaml (because the binding is looking in the local datacontext, not the parent).
I may be going about this all wrong, most examples I've seen seem to instantiate a ViewModel in the custom control xaml, but then I don't see how you get that ViewModel to reference the correct DataModel (or specific part of the datamodel).
The following hopefully explains what I am trying to do.
Firstly I have my data model, in DataModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class DataModel
{
private List<string>[] _dataLists;
public List<string>[] DataLists
{
get { return _dataLists; }
}
public DataModel()
{
List<string> list0 = new List<string> { "One", "Two", "Three" };
List<string> list1 = new List<string> { "Alpha", "Beta", "Gamma" };
_dataLists = new List<String>[] { list0, list1 };
}
}
}
In MainViewModel.cs
namespace BindingTest1
{
class MainViewModel
{
private MyViewModel _myFirstViewModel;
public MyViewModel MyFirstViewModel
{
get { return _myFirstViewModel; }
}
private MyViewModel _mySecondViewModel;
public MyViewModel MySecondModel
{
get { return _mySecondViewModel; }
}
private DataModel _dataModel;
public DataModel DataModel
{
get { return _dataModel; }
}
public MainViewModel()
{
_dataModel = new DataModel();
_myFirstViewModel = new MyViewModel(_dataModel.DataLists[0]);
_mySecondViewModel = new MyViewModel(_dataModel.DataLists[0]);
}
}
}
MainWindow.xaml
<Window x:Class="BindingTest1.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"
xmlns:local="clr-namespace:BindingTest1"
mc:Ignorable="d"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- These were just to check the data was being set up properly -->
<ListBox x:Name="listBox1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[0]}"/>
<ListBox x:Name="listBox2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[1]}"/>
<!-- this is what I want to be able to do -->
<local:MyView ViewModel="{Binding MyFirstViewModel}"/>
<local:MyView ViewModel="{Binding MySecondViewModel}"/>
</StackPanel>
</Grid>
</Window>
(Codebehind is default)
In MyViewModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class MyViewModel
{
private List<string> _dataList;
public List<string> DataList
{
get { return _dataList; }
}
public MyViewModel(List<string> list)
{
_dataList = new List<String>(list);
_dataList.Add("Some Local Processing");
}
}
}
MyView.xaml
<UserControl x:Class="BindingTest1.MyView"
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"
xmlns:local="clr-namespace:BindingTest1"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="100">
<Grid>
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding ViewModel.DataList}"/>
</Grid>
</UserControl>
Codebehind
using System.Windows;
using System.Windows.Controls;
namespace BindingTest1
{
/// <summary>
/// Interaction logic for MyView.xaml
/// </summary>
public partial class MyView : UserControl
{
public MyViewModel ViewModel
{
get { return (MyViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel), typeof(MyView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnViewModelChanged)));
public MyView()
{
InitializeComponent();
}
private static void OnViewModelChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// Just making sure the right thing is being received
List<string> dataList = (e.NewValue as MyViewModel).DataList;
foreach(string line in dataList)
{
System.Console.WriteLine(line);
}
}
}
}
I don't think you need a dependency property here.
Try this.
<local:MyView DataContext="{Binding MyFirstViewModel}"/>
<local:MyView DataContext="{Binding MySecondViewModel}"/>
and bind the DataList to ItemsSource in the MyView XAML.
As you assigned MyFirstViewModel to the DataContext of MyView, bindings inside will look in MyFirstViewModel for the ItemsSource.
Here's how you ought to do this. Your view doesn't need a ViewModel property. It should bind to properties of its DataContext, which will be the viewmodel.
view:
ItemsSource="{Binding DataList}"
Window:
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<local:MyView
/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- ... -->
<ContentControl Content="{Binding MyFirstViewModel}"/>
<ContentControl Content="{Binding MySecondViewModel}"/>
</StackPanel>

How do you put an ObservableCollection into a Textblock

I need to put my ObservableCollection<ValidationMessage> into my TextBlock. Here is my code. Right now it is showing the Item and the SubItems, but where the messages show it has System.Collections.ObjectModel.ObservableCollection'1[ValidationWPF.DataSources.‌​ValidationMessages].
I think this is because it cannot put an ObservableCollection into the TextBlock.
XAML:
<UserControl x:Class="ValidationWPF.ValidationUserControl"
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"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:local="clr-namespace:ValidationWPF.DataSources"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="Messages">
<TextBlock Text="{Binding Message}"/>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<telerik:RadTreeView x:Name="radTreeView" Margin="8">
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
<TextBlock Text="{Binding item}" />
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
</telerik:RadTreeView>
</Grid>
</UserControl>
ValidationMessage Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ValidationWPF.DataSources
{
public class ValidationMessage
{
public ValidationMessage(string Message)
{
this.Message = Message;
}
public string Message
{
get;
set;
}
}
}
ValidationItem Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
namespace ValidationWPF.DataSources
{
public class ValidationItem : ObservableCollection<ValidationItem>
{
public ValidationItem()
{
SubItems = new ObservableCollection<ValidationItem>();
}
public ObservableCollection<ValidationMessage> Message
{
get;
set;
}
public string item
{
get;
set;
}
public IList<ValidationItem> SubItems
{
get;
set;
}
public static IList<ValidationItem> GetItems(string name)
{
var Validation = new ObservableCollection<ValidationItem>();
var item = new ValidationItem();
item.item = "Customer";
var subItem = new ValidationItem();
subItem.item = "Name";
item.SubItems.Add(subItem);
var Message = new ValidationItem();
Message.item = new ObservableCollection<ValidationMessage>().ToString();
subItem.SubItems.Add(Message);
Validation.Add(item);
return Validation;
}
}
}
Thank you for your help!!
The problem is that the Text property of the TextBlock is a string, and you're giving it an ObservableCollection. The only way WPF knows to convert the two is by calling ObservableCollection.ToString(), which returns the full type name of the class.
The fix is to convert your ObservableCollection into a string by creating a class that implements System.Windows.Data.IValueConverter. This allows you to control the conversion.
You could implement it something like this:
using System.Globalization;
using System.Text;
using System.Windows.Data;
namespace ValidationWPF.DataSources
{
class CollectionConverter : IValueConverter
{
object Convert(object value, Type targetType,object parameter,CultureInfo culture)
{
ObservableCollection<ValidationMessage> messages = (ObservableCollection<ValidationMessage>)value;
var sb = new StringBuilder();
foreach(var msg in messages)
{
sb.AppendLine(msg.Message);
}
return sb.ToString();
}
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
And you can use it like this in your XAML file:
<UserControl x:Class="ValidationWPF.ValidationUserControl"
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"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:local="clr-namespace:ValidationWPF.DataSources"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:CollectionConverter x:Key="CollectionConverter" />
<DataTemplate x:Key="Messages">
<TextBlock Text="{Binding Message, Converter={StaticResource CollectionConverter}}"/>
</DataTemplate>
</UserControl.Resources>
...
</UserControl>
Now WPF will call CollectionConverter.Convert() whenever it needs to populate your TextBlock.
You are right. The TextBlock tries to view the property value as a String and an ObservableCollection.ToString will return just what you saw.
What you could do is to add a new property that combines all the messages of the ObservableCollection into a single string. Something like this:
public string MessagesCombined
{
get { return string.Join(Environment.NewLine, Message.Select(m => m.Message)); }
}
This will combine all the Messages in your ObservableCollection into a single string, with each item separated by a newline. (You may have to modify my code somewhat, I am writing this without access to a compiler...).
I ended up doing it a different and cleaner way.
XAML:
<UserControl x:Class="ValidationWPF.ValidationUserControl"
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"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:local="clr-namespace:ValidationWPF.DataSources"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<!--<local:CollectionConverter x:Key="CollectionConverter"/>
<DataTemplate x:Key="Messages">
<TextBlock Text="{Binding Message}"/>
</DataTemplate>-->
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<telerik:RadTreeView x:Name="radTreeView" Margin="8" ItemsSource="{Binding Errors}">
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Description}"/>
<ListBox Grid.Row="1" ItemsSource="{Binding Messages}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Message}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
</telerik:RadTreeView>
</Grid>
</UserControl>
VALIDATIONMESSAGE CLASS:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ValidationWPF.DataSources
{
public class ValidationMessage
{
public ValidationMessage(string name, string Message)
{
this.Message = Message;
this.PropertyName = name;
}
public string Message
{
get;
set;
}
public string PropertyName { get; set; }
}
}
VALIDATIONVIEWMODEL CLASS:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
namespace ValidationWPF.DataSources
{
public class ValidationViewModel
{
public ValidationViewModel()
{
this.Errors = new ObservableCollection<ValidationItem>();
ValidationItem item = new ValidationItem();
item.Description = "Customer";
ValidationMessage msg = new ValidationMessage("FirstName", "First name is required");
item.Messages.Add(msg);
this.Errors.Add(item);
ValidationItem item2 = new ValidationItem();
item2.Description = "Order";
msg = new ValidationMessage("Quantity", "Quantity must be greater than zero");
item2.Messages.Add(msg);
item.SubItems.Add(item2);
}
public ObservableCollection<ValidationItem> Errors { get; set; }
}
}
VALIDATIONUSERCONTROL CLASS:
public partial class ValidationUserControl : UserControl
{
public ValidationUserControl()
{
InitializeComponent();
this.DataContext = new ValidationViewModel();
}
}

Categories

Resources