WPF ObservableCollection in xaml - wpf

I have created an ObservableCollection in the code behind of a user control. It is created when the window loads:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Entities db = new Entities();
ObservableCollection<Image> _imageCollection =
new ObservableCollection<Image>();
IEnumerable<library> libraryQuery =
from c in db.ElectricalLibraries
select c;
foreach (ElectricalLibrary c in libraryQuery)
{
Image finalImage = new Image();
finalImage.Width = 80;
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri(c.url);
logo.EndInit();
finalImage.Source = logo;
_imageCollection.Add(finalImage);
}
}
I need to get the ObservableCollection of images which are created based on the url saved in a database. But I need a ListView or other ItemsControl to bind to it in XAML file like this:
But I can't figure it out how to pass the ObservableCollection to the ItemsSource of that control. I tried to create a class and then create an instance of a class in xaml file but it did not work. Should I create a static resource somehow>
Any help will be greatly appreciated.

Firstly, the ObservableCollection is a local variable. What you need to do is have it as a private global variable and expose it with a public property. You can use the INotifyPropertyChanged interface to have the image data update automagically when the actual collection itself changes.
In your XAML, you then need to set the DataContext to self, and you can then directly bind your public property to the ItemsSource. You may want to use an ItemTemplate for displaying the items in a custom manner.
Cheers,
Adam
Example as requested:
In C#:
public MyWindowClass
{
public ObservableCollection<image> MyImageCollection
{
get;
set;
}
}
In XAML:
<UserControl
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
...
<ListBox ItemsSource="{Binding MyImageCollection}" ItemTemplate="*yourtemplateresource*" />
...
</UserControl>
Now, the reason that I mentioned using INotifyPropertyChanged is that if you try:
MyImageCollection = new ObservableCollection<image>();
The items in the listbox will not automatically update. With an ObservableCollection, however, you do not need to implement INotifyPropertyChanged for basic addition and removal of list items.

You have to set the DataContext of the UserControl to your collection:
DataContext = _imageCollection
You can do that in the UserControl_Loaded() method.
Next you need to bind the ItemsSource of the ListView in the XAML:
<ListView ItemsSource="{Binding}"/>
The {Binding} is equivalent to {Binding .} which binds to the DataContext of the UserControl. If you need "more stuff" in your DataContext you can instead create a class like this:
class ViewModel : INotifyPropertyChanged {
public ObservableCollection Images { get { ... } }
...
}
Use this class for the DataContext:
DataContext = new ViewModel();
And replace the binding to bind to the Images property:
<ListView ItemsSource="{Binding Images}"/>
Then you can add another property to ViewModel:
class ViewModel : INotifyPropertyChanged {
public ObservableCollection Images { get { ... } }
public String Message { get { ... } set { ... } }
...
}
And bind it to a control:
<TextBlock Text="{Binding Message}"/>
Remember to fire the PropertyChanged event when the Message property is changed in ViewModel. This will update the UI when view-model properties are changed by code.

Related

PropertyChanged event always null even after setting DataContext

I have a Model with INotifyPropertyChanged handling copied from tutorials:
public event PropertyChangedEventHandler? PropertyChanged;
protected void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I update a member of the class, I call the handler:
public string? Id
{
get => _id;
set
{
if (value != _id)
{
_id = value;
Notify(nameof(Id));
}
}
}
And in the view code behind I have:
private Goal _goal;
public GoalControl()
{
InitializeComponent();
this._goal = new MyGoal();
this.DataContext = _goal;
Binding binding = new Binding("Text");
binding.Source = _goal.Id;
binding.Mode = BindingMode.TwoWay;
_ = Id.SetBinding(TextBox.TextProperty, binding);
}
But the view doesn't pick up any changes to the field. When I debug, I find that PropertyChanged is always null. How should I set it to a useful value?
This is in a user control, by the way, which will be generated dynamically so I don't think I can do the binding from XAML.
Assuming that Id is a TextBox in your GoalControl, you would bind its Text property to the Id of the MyGoal object in the DataContext like shown below.
You do not set the Source property of the Binding, because the source object should be provided by the current DataContext. Also note that TwoWay is the default binding mode for the TextBox.Text property and does not need to be set explicitly.
public GoalControl()
{
InitializeComponent();
_goal = new MyGoal();
DataContext = _goal;
Binding binding = new Binding("Id");
Id.SetBinding(TextBox.TextProperty, binding);
}
The Binding could as well be written in XAML:
<TextBox Text="{Binding Id}"/>
Since this is in a UserControl, you should however not set the DataContext at all. UserControls, as any other controls, should not have "private" view models like your MyGoal object.
The UserControl would instead expose a dependency property Id, which is bound when you use the control, like
<local:GoalControl Id="{Binding SomeViewModelId}"/>
In the UserControl's XAML, the Binding to the own property would specify the Source object as RelativeSource:
<TextBox Text="{Binding Id,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>

WPF Data binding to observableCollection

Can someone give me a little help with this one please
I'm trying to reorganise an app to MVVM and make better use of data binding, but am struggling with a little issue.
I have a viewmodel class
public class MainWindowViewModel
{
public ObservableCollection<DiagramElement> Elements { get; set; }
public MainWindowViewModel()
{
AppMachineList = new ListOfMachines();
Elements = new ObservableCollection<DiagramElement>();
}
}
in which I create an observablecollection of the DiagramElement class.
public class DiagramElement : Button
{
private Item linkedItem;
public Item LinkedItem
{
get { return this.linkedItem; }
set
{
this.linkedItem = value;
this.DataContext = this;
this.Template = (ControlTemplate)FindResource("ItemTemplate");
}
}
The DiagramElement class just extends the button class and adds its own controlTemplate.
Back in my MainWindow.xaml.cs class, I instantiate the viewmodel and from that, populate a stackpanel in MainWindow.xaml from the ObservableCollection.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
vm.LoadMachines();
foreach(DiagramElement d in vm.Elements)
{
ItemList.Children.Add(d);
}
}
}
<StackPanel x:Name="ItemList" Orientation="Vertical"></StackPanel>
What I want to do is, do away with the foreach loop and the calls to ItemList.Children.Add(). And replace this with a binding to Elements in the viewmodel like below.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
vm.LoadMachines();
this.DataContext = vm;
}
}
<StackPanel x:Name="ItemList" DataContext="{Binding Path=Elements}"</Stackpanel>
I can't get the elements to be added to the Stackpanel, the binding doesn't work. Any help gratefully received.
FYI, having a ViewModel with a collection of UI elements (in your case, buttons) violates the principles of MVVM -- the UI and model should not be co-mingled like this.
But the immediate problem is you cannot use a StackPanel -- it is a control container but does not support binding to lists of items. You need to use some kind of repeater like an ItemsControl.
<ItemsControl ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- your DiagramElement should go here, something like
<DiagramElement LinkedItem={Binding Path=SomePropertyOnYourRevisedElement} />
-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But there's more work to be done. DiagramElement needs to have LinkedItem converted into a DependencyProperty (lots of examples of this online) and Elements needs to be a list of some sort of model object that just stores the properties needed for the DiagramElement (with no UI stuff).

Binding View Model to View in data template

public class ToolBarView : ToolBar
{
public ToolBarView()
{
this.DataContext = new ToolBarViewModel();
}
}
public ToolBarViewModel: ViewModelBase
{
public ObservableCollection<ViewModelBase> Items {get;set;}
public ToolBarViewModel()
{
// populate button view models
Items.Add(new ButtonViewModel() {Content="Button1"});
Items.Add(new ButtonViewModel() {Content="Button2"});
}
}
public class ButtonView : Button
{
public ButtonView()
{
this.DataContext = new ButtonViewModel();
}
}
public class ButtonViewModel : ViewModelBase
{
public object Content {get;set;}
}
In MainWindow.xaml
<Window.Resources>
<DataTemplate x:Key="buttonTemplate" DataType="{x:Type vm:ButtonViewModel}">
<v:ButtonView Content={Binding Content}/>
</DataTemplate>
<v:ToolBarView ItemsSource="{Binding Items}"
ItemTemplate={StaticResource buttonTemplate}/>
Note: I did INotifyChanged in ViewModelBase class
In MainWindow.xaml. i think My template is wrong.ButtonView in DataTemplate is creating a new view instance. It is not binding the viewModel that was poplulated in the ToolBar Items collection. I tried to do with Relative Binding. Still not successful.
Please help me out.
Just drop the line where you create a new VM and overwrite the DataContext:
this.DataContext = new ButtonViewModel();
Then the DataContext will be inherited (it will be the item in the collection, the ButtonVM).
(As a side-note, you seem to try view-first and view-model-first at the same time, you should stick with one. Also the view should probably already bind to all the relevant properties on the view-model so that you just need need to create the view and that's it)

Implementing a "Search as you type" in Silverlight

I am trying to implement a search as you type screen in my Silverlight application. The idea is that I have a screen with a textedit control and a listbox. The listbox is filled with all my data.
When the user types something in the textbox the following happens:
All the items that are not containing all the letters from the user input are hidden.
The matching letters of the visible list items are highlighted with a different color.
I am not sure how to start with this, so all pointers, samples and hints are welcome!
I would suggest using a CollectionViewSource. A CollectionViewSource has the ability to filter items. You can bind your ListBox to the CollectionViewSource and handle Filter event to do the filtering. Bind your "Search Box" to a Text property which you can use in your Filter event. You can handle the "KeyUp" event of the TextBox control to kick off your filtering, by calling the Refresh method on the CollectionViewSource View.
Filtering Data using CollectionViewSource: http://xamlcoder.com/blog/2010/10/27/filtering-data-using-collectionviewsource/
http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource.filter.aspx
http://msdn.microsoft.com/en-us/library/system.componentmodel.icollectionview.aspx
http://timheuer.com/blog/archive/2009/11/04/updated-silverlight-3-datagrid-grouping-data-pagedcollectionview.aspx
http://bea.stollnitz.com/blog/?p=392
Sudo code:
// ViewModel - properties should fire NotifyPropertyChanged
public class ViewModel : INotifyPropertyChanged
{
public ViewModel
{
this.Data = new CollectionViewSource();
this.Data.Source = this.GenerateObjects();
this.Data.Filter += (s,e) =>
{
// TODO: add filter logic
DataObject item = e.Item as DataObject;
return item.Name.Contains(this.SearchText);
};
}
public string SearchText{get;set;}
public CollectionViewSource Data {get;set;}
private List<DataObject> GenerateObjects(){ // generate list of data objects }
}
// View XAML
<StackPanel>
<TextBox Text="{Binding SearchText, Mode=TwoWay}" KeyUp="OnKeyUp"/>
<ListBox ItemsSource="{Binding Data.View}"/>
</StackPanel>
// View Code Behind
public class View : UserControl
{
public View() { this.DataContext = new ViewModel(); }
private ViewModel ViewModel { get { return this.DataContext as ViewModel; } }
private OnKeyUp()
{
this.ViewModel.Data.View.Refresh();
}
}
You may want to start with the AutocompleteBox from the Silverlight Toolkit.
It has a number of handy points where you would be able to extend it's functionality, for example in the instance searching your pool of values.

CheckedItems property for custom CheckBoxList control in Silverlight

I need to implement CheckBoxList control with ItemsSource and CheckedItems properties. Items from ItemsSource should be displayed as checked checkboxes if CheckedItems contains these values or unchecked otherwise. Also I need two-way databinding support for CheckedItems property (value of this property should be updated when user clicks on checkboxes).
Here some code which probably can help to understand my problem
XAML:
<UserControl x:Class="Namespace.Controls.CheckBoxList" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListBox x:Name="LayoutRoot">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Code behind:
public partial class CheckBoxList : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CheckBoxList), null);
public static readonly DependencyProperty CheckedItemsProperty = DependencyProperty.Register("CheckedItems", typeof(IEnumerable), typeof(CheckBoxList), null);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public IEnumerable CheckedItems
{
get { return (IEnumerable)GetValue(CheckedItemsProperty); }
set { SetValue(CheckedItemsProperty, value); }
}
public CheckBoxList()
{
InitializeComponent();
LayoutRoot.SetBinding(ItemsControl.ItemsSourceProperty, new Binding("ItemsSource") { Source = this });
}
}
I think that I need to bind ListBox to UserControl with custom converter, which will return collection of items with additional IsChecked property, but it works only in case of one-way data binding.
Looks like I need two-way binding to two properties at one time, but I don't know how to implement it and will appreciate any help with this issue.
Thanks in advance.
First of all you should consider deriving from ListBox rather than UserControl. The ListBox already does most of what you want.
Secondly consider one way binding to an IList. You can then add and remove entires to that IList as the respective items are selected.
Rather than try to bind a CheckBox control in an Item Template you make a copy of the ListBox styles, place them in Generic.xaml as the style of your new control. Then modify the unselected and selected visual states using a checked and unchecked check box as part of the visual appearance.
Now you can attach to the SelectionChanged event and use the Event args AddedItems list to add to the bound IList and the RemovedItems list to remove items from the bound list.
You would need to clear and re-add the set of items to the list box SelectedItems list when either your CheckedItems is assigned or the ItemsSource is changed.
There are probably a number gotchas that you will need to work round but this seems like a more direct path to your goal than starting from scratch with a UserControl base.
Add an observable collection for your list box datasource to your datacontext:
private ObservableCollection<MyItem> _myItems;
public ObservableCollection<MyItem> MyItems
{
get { return _searchByFields; }
set
{
_myItems = value;
}
}
Add a class to hold the data about your checkboxes:
public class MyItem
{
public bool Checked {get; set; }
public string MyItemValue { set ; set; }
}
Then in your data template bind listbox to the collection and your data template checkboxes to the respective MyItem properties:
<UserControl x:Class="Namespace.Controls.CheckBoxList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListBox x:Name="LayoutRoot"
DataContext="[Dataconext here]"
ItemsSource={Binding MyItems}>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked, Mode=TwoWay}"
Content="{Binding MyItemValue}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Don't forget to set the DataContext of the binding to the appropriate class (you might be doing this in the XAML or the code behind perhaps)

Resources