Quick description of my setup:
Disclaimer: The code is only to show what I want to do. The command binding for instance is done with event triggers etc. I'm pretty sure this wouldn't even build, but I didn't want to waste space.
My View:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding IsFavorite, Converter={StaticResource FavoriteConverter}" Tap="{Binding FavoriteCommand, CommandParameter={Binding}}"/>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My ViewModel:
public class ViewModel : ViewModelBase
{
public IList<Item> Items { get; set; }
public RelayCommand<Item> Favorite
{
get
{
return new RelayCommand<Item>(item =>
{
item.IsFavorite = !item.IsFavorite;
RaisePropertyChanged(string.Empty);
};
}
}
}
My Model:
public class Item
{
public string Title { get; set; }
public bool IsFavorite { get; set; }
}
*My Question:*
How can I get the IsFavorite-Property to update without implementing INotifyPropertyChanged? It's a model class, and I wouldn't like creating a ViewModel for the sole purpose of updating one property. I thought that calling PropertyChanged with an empty string would update everything, but alas it didn't.
*My Solution:*
public class ItemViewModel : ViewModelBase
{
public Item Model { get; private set; }
public bool IsFavorite
{
get { return Model.IsFavorite; }
set
{
Model.IsFavorite = value;
RaisePropertyChanged("IsFavorite");
}
}
}
If you are binding to the value and expect it to be changing at runtime, then you should really implement INotifyPropertyChanged since WPF's binding system uses that interface to know when it needs to update.
But with that said, you might be able to get away with raising a PropertyChange notification for the entire Items collection, although I'm not entirely sure that would work because the actual collection itself hasn't changed, only a property of one of the items inside the collection. And WPF usually knows not to bother re-evaluating a property if it doesn't actually change.
RaisePropertyChanged("Items");
If it doesn't work, you could probably remove the item and re-add it to trigger a CollectionChange notification, however that also might not work and may cause other problems depending on your application design.
// I may have the exact syntax wrong here
var index = Items.IndexOf(item);
var tmp = Items[index];
Items.Remove(tmp);
Items.Add(tmp, index);
But it would really just be best to implement INotifyPropertyChanged on your Item class.
Try to use: RaisePropertyChanged("Items"); and make Items collection ObservableCollection.
Implementing INotifyPropertyChanged is better.
Related
I have created a dynamically generated TabControl by binding ItemsSource to MyUnicornsViewModel.
As new items are added to MyUnicornsViewModel... new tab items are created. However, the newly added tabs are not automatically selected in the TabControl.
How can I get new tabs to be selected when they are added?
<TabControl ItemsSource="{Binding MyUnicornsViewModel}" SelectedItem="{Binding SelectedItem}">
<TabControl.ItemTemplate>
<!-- header template -->
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- body template-->
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
At first, I was hoping there was an event for "ItemsChanged" or "ItemAdded" in the TabControl, that way I can set the SelectedIndex in the code-behind as new items are added.
Another thing I tried was to bind the TabControl.SelectedItem to a SelectedItem property in MyUnicornsViewModel. Sadly, that didn't work either.
MyUnicornsViewModel:
public class MyUnicornsViewModel : ObservableCollection<UnicornViewModel>
{
...
private void AddNewUnicorn()
{
var awesomeUnicorn = new UnicornViewModel();
Add(awesomeUnicorn);
SelectedItem = awesomeUnicorn; //I expected my TabControl to have 'awesomeUnicorn' selected.
}
public UnicornViewModel SelectedItem { get; set; }
}
There are a couple of issues here:
It's very odd to derive a "view model" from ObservableCollection. A view model should contain an observable collection.
View models need to implement the INotifyPropertyChanged interface; it's not clear from the code provide if UnicornViewModel implements this interface, however, MyUnicornsViewModel absolutely does not.
Here's some suggestions:
A view model base class that implements the INotifyPropertyChanged interface will really help get you most of the way. You can write your own using the INotifyPropertyChanged documentation or look for an MVVM framework that fits well with your project (e.g. Prism, MVVM Light, ReactiveUI). Each of these will provide a base class to use for view models - BindableBase, ViewModelBase, ReactiveObject respectively for each of the frameworks above.
MyUnicornsViewModel should have:
An ObservableCollection for the collection of unicorns; this will be bound to the ItemsSource property on your TabControl.
The SelectedItem property must fire the PropertyChanged event when set.
Here's a quick sample using Prism:
public sealed class UnicornViewModel : BindableBase
{
public UnicornViewModel(string name, string content)
{
Name = name;
Content = content;
}
// these properties don't change and therefore don't need to raise property changed
public string Name { get; }
public string Content { get; }
}
public sealed class UnicornsViewModel : BindableBase
{
private UnicornViewModel _selectedUnicorn;
public UnicornsViewModel()
{
AddUnicornCommand = new DelegateCommand(AddUnicorn);
ClearUnicornsCommand = new DelegateCommand(ClearUnicorns, () => HasUnicorns).ObservesProperty(() => HasUnicorns);
}
public ObservableCollection<UnicornViewModel> Unicorns { get; } = new ObservableCollection<UnicornViewModel>();
public UnicornViewModel SelectedUnicorn
{
get => _selectedUnicorn;
set => SetProperty(ref _selectedUnicorn, value, () => RaisePropertyChanged(nameof(HasUnicorns)));
}
public DelegateCommand AddUnicornCommand { get; }
public DelegateCommand ClearUnicornsCommand { get; }
private bool HasUnicorns => Unicorns.Any(); // helper property for the clear command's can execute
private void AddUnicorn()
{
Unicorns.Add(new UnicornViewModel($"Unicorn {Unicorns.Count + 1}", Guid.NewGuid().ToString()));
SelectedUnicorn = Unicorns.Last();
}
private void ClearUnicorns()
{
SelectedUnicorn = null;
Unicorns.Clear();
}
}
I'm trying to implement a Directory Tree View that also shows all the files in my MVVM project. My Folder and Files structure in the Model is like this:
public class UserDirectory
{
private ObservableCollection<UserFile> files;
private ObservableCollection<UserDirectory> subfolders;
private String directoryPath;
//public getters and setters...
}
public class UserFile
{
private String filePath;
private Category category; //Archive, Document, Exe, etc...
//public getters and setters
}
I'd like to show them in a TreeView, but after reading this very helpful Josh Smith article, and various other sources, I still don't know how to work it out with HierarchicalDataTemplate.
Possible solution
I've figured out that maybe I have to create a specific type, like Item, that exists only for showing the name of the files and the directories,
public class Item
{
private List<String> directories;
private List<String> files;
}
but I'd like to reuse my class structure, because I need to show the Category data of the UserFile, for example.
The question
How can I show the files and the subfolders while maintening my current Data Structure?
This is an example of what I want to reach (I'm sorry but image uploading isn't working)
XAML
<TreeView
ItemsSource="{Binding RootDirectoryItems}"
>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:UserDirectory}" ItemsSource="{Binding Items}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:UserFile}">
<Label Content="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
RootDirectoryItems is presumed to be a property of the viewmodel, something like this:
public ObservableCollection<Object> RootDirectoryItems { get; }
= new ObservableCollection<object>();
In the C#, assume the presence of INotifyPropertyChanged boilerplate on all property setters. I added two properties to UserDirectory:
Name, a readonly property which returns just the name segment of DirectoryPath. If DirectoryPath may change at runtime, its setter should call OnPropertyChanged("Name");, so that bindings looking at the Name property will know they need to get the new value. UserFile gained a similar Name property, which comes with the same advice about raising PropertyChanged if that's a possibility.
Items: Again, a readonly property, and you should raise PropertyChanged appropriately if either of the constituent collections changes (handle ICollectionChanged.CollectionChanged, and do likewise in the setters if you have setters). Bindings don't care about the declared type of a property, so it just returns System.Collections.IEnumerable -- it could even return object, and the XAML wouldn't care. But let's be just specific enough, without being so specific as to encourage anybody in C# to try to use the property for anything.
If it were me, I'd almost certainly make UserDirectory and UserFile bare immutable "POCO" classes without INotifyPropertyChanged, and simply repopulate if anything changed on the disk. I might depart from immutability by giving UserDirectory a FileWatcher and having it repopulate itself, if I had some reason to expect directories to change a lot.
So here's the C#:
public class UserDirectory
{
public ObservableCollection<UserFile> Files { get; set; } = new ObservableCollection<UserFile>();
public ObservableCollection<UserDirectory> Subfolders { get; set; } = new ObservableCollection<UserDirectory>();
// Concat demands a non-null argument
public IEnumerable Items { get { return Subfolders?.Cast<Object>().Concat(Files); } }
public String DirectoryPath { get; set; }
public String Name { get { return System.IO.Path.GetFileName(DirectoryPath); } }
}
public class UserFile
{
public String FilePath { get; set; }
public Category Category { get; set; }
public String Name { get { return System.IO.Path.GetFileName(FilePath); } }
}
Your Item class isn't needed, because XAML works by "duck typing".
Here's a simpler variant that also works, because both UserDirectory and UserFile have a Name property, and UserFile's missing Items property is quietly shrugged off.
<TreeView
ItemsSource="{Binding RootDirectoryItems}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
At the moment i try, to build something like that with WPF ! Screenshot: http://i.imgur.com/5G6xBTu.png
I have a ObservableCollection with my "Wecker" Objects. I want to dynamicly add items to the listbox with DataBinding that looks like in the Screenshot. Every try failed so far. What do i need to set in the XAML File??
public static ObservableCollection<Wecker> WeckerCollection = new ObservableCollection<Wecker>();
public ObservableCollection<Wecker> MyWeckerCollection
{
get { return WeckerCollection; }
}
Wecker Class
public class Wecker
{
public ArrayList dayOfWeek { get; set; }
public DateTime Alarm { get; set; }
public bool activated { get; set; }
public bool loop { get; set; }
public int maxRunTime { get; set; }
public int id { get; set; }
public bool schlummern { get; set; }
public bool antiStandby { get; set; }
public bool activateMonitor { get; set; }
public string fileName { get; set; }
public string Mp3 { get; set; }
public string Message { get; set; }
public bool ShowMessage { get; set; }
public int volume { get; set; } }
I tryed that last time:
<ListBox HorizontalAlignment="Left" Height="392" VerticalAlignment="Top" Width="431" Margin="15,89,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding MyWeckerCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding activated, Mode=TwoWay}" />
<Label Content="{Binding Alarm}" />
<Label Content="{Binding dayOfWeek}" />
<Label Content="{Binding Message}" />
<Label Content="{Binding Mp3}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I suspect you have not set the DataContext
In the ctor set the DataContext
this.DataContext = this;
or you can do it in XAML in the Window (top) section
DataContext="{Binding RelativeSource={RelativeSource self}}"
If you had set the DataContext then that should work
Are you sure it is in the Windows section
Try (but Path is the default property so that should not be a problem)
ItemsSource="{Binding Path=MyWeckerCollection}"
This may be your problem - public static?
What is the purpose of public static here?
public static ObservableCollection<Wecker> WeckerCollection = new ObservableCollection<Wecker>();
If you want to use a backing property then do it like this
private ObservableCollection<Wecker> myWeckerCollection = new ObservableCollection<Wecker>();
public ObservableCollection<Wecker> MyWeckerCollection
{
get { return myWeckerCollection ; }
}
It sounds like your DataContext is set incorrectly.
You say you are binding the DataContext to {Binding RelativeSource={RelativeSource Self}}, however that just binds the DataContext to the UI object itself.
For example,
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
would set the DataContext to the Window object, however the class Window does not have a property called MyWeckerCollection, so your binding would fail.
If you had
<local:MyCustomWindow DataContext="{RelativeSource={RelativeSource Self}}">
and MyCustomWindow has a property called MyWeckerCollection, then it would work.
I also see your comment here which states:
I am not getting any Data at all and i checked the object, "WeckerCollection" it has Data BEFORE setting it as the DataContext
This leads me to believe that either
A) MyWeckerCollection is not a UI control, in which case you need to update your DataContext binding to something other than Self so it correctly binds to your object containing MyWeckerCollection instead of to the UI object.
B) Or this comment can be read as you are setting the DataContext to MyWeckerCollection itself, and of course the class ObservableCollection<Wecker> does not itself have a property called MyWeckerCollection, so the binding would fail.
So the root cause of your problem is the DataContext is not being set correctly.
Unfortunately, the information you provided is not enough for us to help to identify the correct way to set the DataContext, however if you can provide us with more information I'd be glad to help you out.
Often Visual Studio's binding errors and/or Debug mode is enough to point you in the right direction for fixing the DataContext, or there are some 3rd party tools out there like Snoop which I'd highly recommend for debugging binding errors.
Also if you're new to WPF (which it sounds like you are), and are struggling to understand the purpose of the DataContext and how it works, I'd suggest a blog article of mine written for beginners: What is this "DataContext" you speak of?. Its very important that you understand the DataContext if you are going to be working with WPF. :)
Try to add the ItemSource to your ListBox and change the Xaml like this :
Code behind :
this.YourList.ItemsSource = WeckerCollection;
Xaml :
<ListBox HorizontalAlignment="Left" Height="392" VerticalAlignment="Top" Width="431" Margin="15,89,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible">
Xaml code for listview is some thing like this. I populate Id,name and email in each item of the listview.Listview name is resultview.There is a textbox to search.
<TextBox Text="{Binding SearchText, Mode=TwoWay,Source=PropertyChanged}"/>
<ListView Name="ResultsView" IsItemClickEnabled="True" Margin="0,65,4,0" SelectionChanged="ResultsView_SelectionChanged_1" ItemsSource="{Binding contacts}" Background="White" ItemClick="ResultsView_ItemClick" Loaded="ResultsView_Loaded">
<ListView.ItemTemplate>
<TextBlock x:Name="st1" Text="{Binding id}" FontSize="28" Grid.Row="0" Foreground="Black"/>
<TextBlock x:Name="st2" Text="{Binding name}" FontSize="22" Grid.Row="1" Foreground="Black"/>
<TextBlock x:Name="st3" Text="{Binding email}" FontSize="22" Grid.Row="2" Foreground="Black"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is the class
public class Phone
{
public string mobile { get; set; }
public string home { get; set; }
public string office { get; set; }
}
public class Contact
{
public string id { get; set; }
public string name { get; set; }
public string email { get; set; }
public string address { get; set; }
public string gender { get; set; }
public Phone phone { get; set; }
}
public class RootObject
{
public ObservableCollection<Contact> contacts { get; set; }
}
The data context for Resultview is set as followed in my application.
var _Data = JsonConvert.DeserializeObject<RootObject>(jsonString);
ResultsView.DataContext = _Data;
Now the thing i need is when the text in the textbox changes listview items should filter according to the name.
As you told i added this part. bt it shows errors.errors are in comment lines
public string SearchText
{
get
{
return SearchText;
}
set
{
if(SearchText == value)
return;
SearchText = value;
Contacts.Clear();//Here it says contacts doesnt exists in the current context.
foreach(var item in ResultsView.where(contact => contact.Name.Contains(SearchText)))//it shows windows.UI.XAML.Listview does not contaain definion for where
{
Contacts.Add(item);
}
}
}
I am getting those two errors.And i am workin on windows store app not on windows phone.this is all what i have done. if it doesnot work out please provide me an alternate solution.
-Thanks
Here is what you need:
A ViewModel (you should be using Mvvm)
A CollectionViewSource
A method of selecting what to filter by
A method of filtering your data
Create a ViewModel. This is where your Contacts are. Create an ObservableCollection<Contact> Contacts. At initialization, fill this ObservableCollection as you would like. Save off a copy of the base list as _baseContactList;
In your View, create a CollectionViewSource as a StaticResource in your Page.Resources. Bind its contents to your Collection.
Set your ItemsSource of your List to the CVS.
Now, you need to create a method of determining what to sort. You may do something like bind a ComboBox to a string property in your ViewModel. The setter of that property will change the values of your backing ObservableCollection similar to this:
public string SearchText
{
get
{
return _searchText;
}
set
{
if(_searchText == value)
return;
_searchText = value;
Contacts.Clear();
foreach(var item in _baseContactList.Where(contact =>
contact.Name.Contains(_searchText))
{
Contacts.Add(item);
}
}
Now, this is pretty horribly inefficient and may not be idea for you. Firstly, it clears the search after every change. You'll probably want to do something like throttling it with ReactiveExtensions so that it only refills half a second after the last keystroke. Secondly, you'll likely want to replace the condition (contact.Name.Contains(_searchText)) with your custom criteria, possibly including checking the email as well. You may also want to remove case sensitivity (by comparing the ".ToLower()" version of Name and _searchText, or whichever two you're comparing).
So, in order to make this happen, you'll need a TextBox which connects its current string to SearchText. Something like this:
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The Binding statement targets the SearchText property, makes sure that any changes to the TextBox gets reflected in SearchText (via Mode=TwoWay) and then tells it to send the change every time the text changes (at each keystroke) by setting the UpdateSourceTrigger.
Some future work you may want to add would be to have the clearing and refilling be both throttled and asynchronous, along with using an IncrementallyLoadingObservableCollection. Using the incremental loader will actually make it so that you don't necessarily need to throttle as much, as it should only load about 5 at a time. You can look up guides for making one.
That should be it (aside from styling each item). Hope this gets you on your way!
I am trying to implement the MVVM design patern for mt WPF application. In order to connect the view to the viewmodels, I use a ResourceDictionary (used in Application.Resources), that looks like
<DataTemplate DataType={x:Type viewmodel:SampleViewModel}>
<view:SampleView1 />
</DataTemplate>
The view models are then simply put into content presenters to display them.
Now, when the user presses a button, I'd like to display SampleViewModel using a different view. How do I change the data template used for SampleViewModel?
Less words more code.
As far as you said, you have the class SampleViewModel. I added the property Title for demonstration and ViewType for identifying the correct view:
public enum ItemViewType { View1, View2 };
public class SampleViewModel
{
public string Title { get; set; }
public ItemViewType ViewType { get; set; }
}
The DataTemplateSelector for two views depending on the ViewType property:
class ItemViewTemplateSelector : DataTemplateSelector
{
public DataTemplate View1Template { get; set; }
public DataTemplate View2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var vm = item as SampleViewModel;
if (vm == null)
return null;
switch (vm.ViewType)
{
case ItemViewType.View1:
return View1Template;
case ItemViewType.View2:
return View2Template;
}
return null;
}
}
Xaml code:
<Window.Resources>
<DataTemplate x:Key="view1Template">
<TextBlock Text="{Binding Title}" Foreground="Red"/>
</DataTemplate>
<DataTemplate x:Key="view2Template">
<TextBox Text="{Binding Title}" />
</DataTemplate>
<local:ItemViewTemplateSelector x:Key="viewTemplateSelector"
View1Template="{StaticResource view1Template}"
View2Template="{StaticResource view2Template}"/>
</Window.Resources>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel>
<Button Content="ChangeView" HorizontalAlignment="Center" Command="{Binding SwitchViewCommand}"/>
<ContentControl Content="{Binding ItemViewModel}" ContentTemplateSelector="{StaticResource viewTemplateSelector}"/>
</StackPanel>
The main part is in the class MainViewModel where I've put the logic for switching views:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
this.ItemViewModel = new SampleViewModel { Title = "Some title", ViewType = ItemViewType.View1 };
this.SwitchViewCommand = new RelayCommand(() =>
{
this.ItemViewModel.ViewType = this.ItemViewModel.ViewType == ItemViewType.View1
? ItemViewType.View2
: ItemViewType.View1;
//The magic senquence of actions which forces a contentcontrol to change the content template
var copy = this.ItemViewModel;
this.ItemViewModel = null;
this.ItemViewModel = copy;
});
}
public RelayCommand SwitchViewCommand { get; set; }
private SampleViewModel itemViewModel;
public SampleViewModel ItemViewModel
{
get { return itemViewModel; }
set
{
itemViewModel = value;
RaisePropertyChanged("ItemViewModel");
}
}
}
The SwitchViewCommand can be any type of command, I use the command from the mvvmlight library.
Inside the handler of the command I change the type of viewmodel and update the property ItemViewModel in a tricky way because a ContentControl refreshes a view only if to change the Content property, and this property will not be changed unless you set a reference to different object.
I mean, even the code this.ItemViewModel = this.itemViewModel will not change the view.
It's strange, but the workaround doesn't require much work.
You can achieve this in many different ways depends upon the architecture you want.
You can write a custom DataTemplateSelector and use it on ContentControl.ContentTemplateSelector and choose those two templates appropriately
If this pattern of changing the view occures in many different places and more frequent UX, I would also recommend those two views toggled using a DataTemplate.DataTrigger based on a property in SampleViewModel [I am guessing you might have a distinguishing property in the ViewModel to know that state]
You can override the mapping by placing a similar resource lower down in the tree. Since WPF will resolve the resource by searching upwards, such an override will replace your existing mapping.