I have 2 listBoxes, if you click an item in the top one, then the bottom one filters to a few results.
I am trying to learn WPF and MVVM and am wondering if this is the correct way to do this. Is this the best way?
Here is what I did:
class VisitInfoViewModel : ViewModelBase
{
List<ServiceType> serviceTypes;
List<ServiceType> allServiceTypes;
public VisitInfoViewModel()
{
ServiceCategories = ServiceCategory.Categories;
allServiceTypes = ServiceType.ServiceTypes;
}
public List<ServiceCategory> ServiceCategories { get; set; }
public List<ServiceType> ServiceTypes
{
get
{
return serviceTypes;
}
}
public ServiceCategory SelectedServiceCategory
{
get { return null; }
set
{
serviceTypes = allServiceTypes.FindAll(st => st.ServiceCategoryGuid.Equals(value.Guid));
OnPropertyChanged("ServiceTypes");
}
}
}
and MainWindow.xaml snippet
<ListBox ItemsSource="{Binding Path=VisitInfo.ServiceCategories}"
SelectedItem="{Binding Path=VisitInfo.SelectedServiceCategory}"
ItemTemplate="{StaticResource listBoxTemplate}"
Height="112"
HorizontalAlignment="Left"
Margin="6,30,0,0"
Name="lbxServiceCategory"
VerticalAlignment="Top"
Width="366" />
<ListBox ItemsSource="{Binding Path=VisitInfo.ServiceTypes}"
ItemTemplate="{StaticResource listBoxTemplate}"
HorizontalAlignment="Left"
Margin="6,0,0,19"
Name="lbxServiceType"
Width="366"
Height="121"
VerticalAlignment="Bottom" />
also, why shouldn't I just add an EventHandler for selectedItemChanged on my listBox?
It seems so much simpler and clearer to use the event handler.
I think it is because if I did that it would no longer by MVVM... is that correct?
What would you do and what are the best practices?
What you are doing is mostly fine - though I would personally make the SelectedServiceCategory a "real" property (with a value that's saved).
The difference with MVVM, and doing it in code behind, is that you're working with data. If you make the "Current Category" change the types, then you're working purely with the data, and not worrying about the UI at all. You can change the category by any mechanism, and the UI will always stay up to date.
I, personally, would suggest writing this more like so:
class VisitInfoViewModel : ViewModelBase
{
List<ServiceType> allServiceTypes;
public VisitInfoViewModel()
{
ServiceCategories = ServiceCategory.Categories;
allServiceTypes = ServiceType.ServiceTypes;
}
// This can use a private setter...
public IEnumerable<ServiceCategory> ServiceCategories { get; private set; }
private ServiceCategory currentCategory;
public ServiceCategory CurrentServiceCategory
{
get { return this.currentCategory; }
set
{
if (this.currentCategory != value)
{
this.currentCategory = value;
ServiceTypesInCurrentCategory = allServiceTypes.Where(st => st.ServiceCategoryGuid.Equals(this.currentCategory.Guid));
OnPropertyChagned("CurrentServiceCategory");
OnPropertyChanged("ServiceTypes");
}
}
}
public IEnumerable<ServiceType> ServiceTypesInCurrentCategory { get; private set; }
}
This provides complete freedom to change the CurrentServiceCategory in code or via Xaml, without any event handlers. It also makes your ViewModel completely data related - you don't know or care what is being used to display this - as long as you have something in your View that sets the CurrentServiceCategory, everything stays synchronized correctly.
also, why shouldn't I just add an EventHandler for selectedItemChanged on my listBox? It seems so much simpler and clearer to use the event handler. I think it is because if I did that it would no longer by MVVM... is that correct?
You can do that, but it's typically a violation of MVVM at this point. The main issue is that you'd be coupling the implementation to that event handler - by doing this, you're basically "locking in" the behavior based on your code in your View for this specific implementation of the View. By keeping it "pure" in terms of MVVM, you're View is free to change (ie: maybe you want to switch to a combobox for the ServiceCategories someday) without touching your ViewModel code at all...
Related
I have been going through posts for 3 hours now with no resolution. I am new to WPF and created the ComboBox below:
Unfortunately I cannot disable the highlighting of the selected item. Does anyone have a viable solution?
Code:
<StackPanel Grid.Column="1"
Margin="800,0,0,0"
Width="135"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<ComboBox Name="LangComboBox"
IsEditable="True"
IsReadOnly="True"
Text="Select Language">
<ComboBoxItem>English</ComboBoxItem>
<ComboBoxItem>Spanish</ComboBoxItem>
<ComboBoxItem>Both</ComboBoxItem>
</ComboBox>
</StackPanel>
I would like to clarify first of all that mine wants to be constructive answer and want to try to spread the culture of good programming.
We all have always to learn about programming, me too!
If you do not know a topic, it is good practice to study perhaps starting from a good book or from the official documentation of the platform.
That said let's move on to some possible approaches to your problem.
First of all, the fact that the selection in the combobox is that way is due to the basic template of the combobox that I invite you to view: https://msdn.microsoft.com/library/ms752094(v=vs.85).aspx )
What you are looking for is a different behavior of the combobox:
Allow display of a default value
Once an element is selected, the text inside it is not underlined
A first approach could be based on the ComboBox template: the combobox is constructed in such a way that, if it is editable, its template
contains a textbox called PART_EditableTextBox
by acting on the textbox, for example by making it disabled, you can get the result you want.
And this can be implemented in different ways:
Inserting a code-behind event handler that disables the textbox when the combobox is loaded
With an Attached behavior that allows you to add custom behaviors to the controls (https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF)
Write a custom control that maybe insert a watermark type part to your combobox
Now consider the first approach that is the fastest to implement so the code could be the following:
<ComboBox Name="LangComboBox" IsEditable="True" IsReadOnly="True"
Loaded="LangComboBox_Loaded"
Text="Select language">
<ComboBoxItem Content="English"/>
<ComboBoxItem Content="Spanish"/>
<ComboBoxItem Content="Both"/>
</ComboBox>
In the code-behind:
private void LangComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox ctrl = (ComboBox)sender;
TextBox Editable_tb = (TextBox)ctrl.Template.FindName("PART_EditableTextBox", ctrl);
if (Editable_tb != null)
{
// Disable the textbox
Editable_tb.IsEnabled = false;
}
}
This approach, however, has drawbacks, among which the fact that if the user wants to deselect / reset the value of the combo can not do it.
So you could follow another path using the MVVM pattern.
Coming from the world of web programming you should know the MVC pattern, in WPF the most common pattern is MVVM or Model - View - ViewModel
between the two patterns there are different things in common and I invite you to take a look at them: Mvvm Pattern.
You could create a class with the model that will be hosted in the combo for example:
public class Language
{
public int Id { get; set; }
public string Description { get; set; }
public Language(int id, string desc)
{
this.Id = id;
this.Description = desc;
}
}
public class YourDataContext : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<Language> _Languages;
public List<Language> Languages
{
get
{
return _Languages;
}
set
{
_Languages = value;
OnPropertyChanged("Languages");
}
}
private Language _selectedLanguage;
public Language SelectedLanguage
{
get
{
return _selectedLanguage;
}
set
{
_selectedLanguage = value;
OnPropertyChanged("SelectedLanguage");
}
}
public YourDataContext()
{
// Initialization of languages
Languages = new List<Language>();
Languages.Add(new Language(0, "None - Select a Language"));
Languages.Add(new Language(1, "English"));
Languages.Add(new Language(2, "Spanish"));
Languages.Add(new Language(3, "Both"));
SelectedLanguage = Languages.First();
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
// some other properties and commands
}
// Your Window class
public MainWindow()
{
InitializeComponent();
var dc = new YourDataContext();
DataContext = dc;
}
<ComboBox ItemsSource="{Binding Languages}"
DisplayMemberPath="Description"
SelectedItem="{Binding SelectedLanguage}"/>
Note that now the combobox is no longer editable and it is possible to reset the selection.
You can manage the selection using the model:
if(dc.SelectedLanguage.Id == 0)
{
//No language selected
}
There are a lot of different ways to achieve what you want, i hope this gave you some good point to start from.
Good programming to everyone.
interface ICar
{
UserControl SmallView{ get; }
UserControl CompleteView{ get; }
}
class ViewModel
{
ObservableCollection<ICar> Cars{ get; set;}
ObservableCollection<UserControl> SmallViews{ get; }
ObservableCollection<UserControl> CompleteViews{ get; }
}
XAML
<ItemControl ItemsSource="{Binding SmallViews}"/>
<ItemControl ItemsSource="{Binding CompleteView}"/>
I am adding ICars instances to ViewModel.Cars collection. When that happens I want the two UserControls (small and Complete) to be added in the View (XAML).
-I can get it to work as I want, by setting the ItemsSources in CodeBehind when Cars.CollectionChanged is Raised. But I fear all the collection is redrawn for all items in ItemsSource.. I only want the changes to be added, and I would like en elegant solution without a lot of CodeBehind.
This Codebehind makes it work as intended - but I would like something cleaner somthing with real Binding.
CompleteControls and SmallControls are the names for the ItemControls above, which in this solution has no binding markup :-( .
public CarsView(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
UpdateViews(viewModel.Cars);
viewModel.Cars.CollectionChanged += (caller, args) =>
UpdateViews(args.NewItems.Cast<ICar>());
}
private void UpdateViews(IEnumerable<ICar> newCars)
{
foreach (var car in newCars)
{
CompleteControls.Items.Add(car.CompleteView);
SmallControls.Items.Add(car.SmallView);
}
}
I see a couple of flaws in your concepts.
class ViewModel
{
ObservableCollection<ICar> Cars{ get; set;}
// This is wrong for MVVM. You don't need this.
//
// ObservableCollection<UserControl> SmallViews{ get; }
// ObservableCollection<UserControl> CompleteViews{ get; }
}
It will also get rid of this.
UpdateViews(viewModel.Cars);
viewModel.Cars.CollectionChanged += (caller, args) =>
UpdateViews(args.NewItems.Cast<ICar>());
About the ObvservableCollection you need to know 2 things.
If you create it directly from another collection it will not trigger the event.
Your args.NewItems.Cast() will always produce one item only in a collection since the ObservableCollection does not have a AddRange() method.
I see that you came from Winforms. Start by inspecting more about MVVM. it will pay off very fast. You need to remember that if you are doing anything in the code behind with controls, stop doing it, cause your doing it wrong.
You will instantiate the UserControls in XAML.
<Listbox ItemsSource={Binding Cars}>
<Listbox.ItemsTemplate>
<DataTemplate>
<StackPanel>
<my:SmallViews />
<my:CompleteViews />
</StackPanel>
</DataTemplate>
</Listbox.ItemsTemplate>
</Listbox>
You should really avoid ever dealing with any UI elements in code behind
Whenever I used TreeView I always had just few nodes and each of them usually had less than 100 items. I never really needed any kind of ui virtualization for that but now for the first time I need it.
The problem appears when using ui virtualization with recycling mode the TreeView seems to expand items even though I never expanded them manually.
I googled the issue and as far I understood recycling mode of virtualization in TreeView the containers get reused.
So I assume that the cause might be applying already expanded reused container to an item which wasn't expanded before.
Here is a simple example:
https://github.com/devhedgehog/wpf/
For those who cannot download code for whatever reason here is basically what I have tried to do with the TreeView.
This is what I have in XAML.
<Grid>
<TreeView ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Parts}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And this is code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IList<Car> list = new List<Car>();
for (int i = 0; i < 5000; i ++)
{
list.Add(new Car() { Name = "test1" + i });
}
foreach (var car in list)
{
car.Parts = new List<string>();
for (int i = 0; i < 500; i++)
{
car.Parts.Add("asdf" + i);
}
}
this.DataContext = list;
}
}
public class Car
{
public string Name
{
get;
set;
}
public List<string> Parts
{
get;
set;
}
}
I hope somebody can provide me a solution to this issue. Is this a known bug?
I am sorry in case its a duplicate. Futhermore I hope you guys tell me what I did wrong since this is my first post before you downgrade the question.
As you probably know, this problem can be solved easily by using standard recycling mode:
<TreeView VirtualizingStackPanel.VirtualizationMode="Standard" ...>
This shouldn't have too much of an impact on your TreeView's performance, as the tree will still be virtualized and a container will only be created for visible items. The benefits of the recycling mode only come into play when scrolling (when items are both being virtualized and realized), and usually the standard virtualization mode is good enough.
However, in case performance is really critical (or if you really want a solution for this while keeping the recycling mode, or if you're looking to do things the right way), you can use backing data and data binding to solve this problem.
The reason why this problem occurs in the first place is this:
Let's say you have a TreeViewItem which has its IsExpanded property set to true. When it's being recycled, i.e. its data is replaced, its IsExpanded property remains the same because it has no way to know whether it should be expanded or not, because that data is not available anywhere. The only place where it exists is the IsExpanded property of the TreeViewItem, and it's not going to be relevant because that item is being reused along with its properties.
If however you have a viewmodel for each tree item you'll be able to bind each TreeViewItem to the IsExpanded property in your TreeViewItemViewModel (you will have a view model for each tree item) and you will always get the correct value because you've made that data available and bound each item to it.
Your TreeView's ItemsSource will be bound to a collection of TreeViewItemViewModel objects, and your TreeViewItemViewModel class will look something like this:
class TreeViewItemViewModel : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
ObservableCollection<TreeViewItemViewModel> Children { get; }
}
You can find more information on how exactly to create such view model in Josh Smith's excellent article Simplifying the WPF TreeView by Using the ViewModel Pattern.
There are 3 UserControls under a MainWindow. Each control have it's own Save Button. The Mainwindow has a SaveAll button.
The MainWindow has a ContentControl and the content property is binded to the VM. At runtime on ButtonClickCommand, the View is instantiated and assigned to the Content Property.
This SaveAll button will internally call methods associated with UserControls Save button. As such, SaveAll doesn't have it's own Method.
This has to be implemented by DependencyProperty.
I had once seen this scenario implemented in a Business App, but somehow missed the concept behind it.
I can't get what was the logic behind this, but it's a very useful thing.
Now I have to implement this, but i'm missing a small thing, I dont know.
I hope the scenario is clear.
Please help me in this scenario, with code.
Thanks,
VJ
Since you mentioned MVVM, here's what you might be looking for. Mind you, this will be a lot cleaner and easier if you use an MVVM framework such as Caliburn, but for this sample, its just vanilla MVVM:
public class MainViewModel
{
public MainViewModel()
{
ViewOneModel = new SubViewModel();
ViewTwoModel = new SubViewModel();
Children = new List<SubViewModel>(new[] { ViewOneModel, ViewTwoModel });
}
public void SaveAll()
{
foreach(var child in Children)
{
child.Save();
}
}
public IList<SubViewModel> Children { get; private set; }
public SubViewModel ViewOneModel { get; set; }
public SubViewModel ViewTwoModel { get; set; }
}
public class SubViewModel
{
public void Save()
{
}
}
and on the UI you basically have subviews (UserControls) composed in your main view:
<StackPanel>
<Button Width="100" Height="20" Content="Save All" />
<local:ViewOne DataContext="{Binding ViewOneModel}" />
<local:ViewTwo DataContext="{Binding ViewTwoModel}" />
</StackPanel>
You just need to bind the save methods to your buttons using an ICommand interface (preferably RelayCommand instance).
Imho in this scenario there is no need for RoutedEvents. The way I would solve it:
There is a Main-ViewModel that exposes 3 properties with the Sub-ViewModels.
The MainViewModel is the Datacontext for the window, and the subviewmodels bound to the datacontext of the 3 usercontrols.
The sub vm's are exposing a property with a Save-Command. This command is bound to the save buttons in the usercontrols.
The main vm is exposing a property with a saveall-command, which is bound to the SaveAll button.
In the handler of the save all command you are then iterating over the sub-vm's and call save on them.
I've been reading a lot about MVVM (using Laurent Bugnion's library in specific) and I'm constantly struggling to determine how to do things in MVVM that were otherwise easy with code behind.
Here's just one example where I suspect I'm doing things the hard way. If anyone has the time to read all this, perhaps they can comment on the sanity of my approach. :)
I have a list box bound to a ViewModel like so:
<ListBox x:Name="lstFruitBasketLeft" ItemsSource="{Binding FruitBasket}"
SelectedItem="{Binding SelectedFruit, Mode=TwoWay}" Width="150">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
HorizontalAlignment="Left" Margin="2">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=":" />
<TextBlock Text="{Binding Quantity}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The ItemSource is an ObservableCollection of Fruit objects:
public class Fruit
{
public string Name { get; set; }
public int Quantity { get; set; }
public Fruit() { }
public Fruit(string name, int quantity)
{
this.Name = name;
this.Quantity = quantity;
}
}
It is defined in the ViewModel as:
// Property FruitBasket
public const string FruitBasketPropertyName = "FruitBasket";
private ObservableCollection<Fruit> _fruitBasket = null;
public ObservableCollection<Fruit> FruitBasket
{
get { return _fruitBasket; }
set
{
if (_fruitBasket == value)
return;
_fruitBasket = value;
// Update bindings, no broadcast
RaisePropertyChanged(FruitBasketPropertyName);
}
}
The bound SelectedItem property is as such:
//Property SelectedFruit
public const string SelectedFruitPropertyName = "SelectedFruit";
private Fruit _selectedFruit = null;
public Fruit SelectedFruit
{
get { return _selectedFruit; }
set
{
if (_selectedFruit == value)
return;
var oldValue = _selectedFruit;
_selectedFruit = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedFruitPropertyName);
}
}
Then, the list is populated on the construction of the ViewModel.
Now, I add a RelayCommand to a button on the presentation page that executes a method which increments the quantity of the selected item. Note that I am not using the parameter yet, but "Bob" is a placeholder for some changes for later.
<Button x:Name="butMore" Content="More!" HorizontalAlignment="Right" Height="25" Width="75" Margin="4">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand
Command="{Binding addMoreCommand}"
CommandParameter="Bob" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Here's the code for the command:
// Property addMoreCommand
public RelayCommand addMoreCommand
{
get;
private set;
}
...
//Init relays (this is in the constructor)
addMoreCommand = new RelayCommand(AddFruit, CanExecute);
...
public void AddFruit()
{
//Increment the fruit
SelectedFruit.Quantity++;
//Save the previous selected item
Fruit oldSelectedItem = SelectedFruit;
//We have to have a new list in order to get the list box to refresh
FruitBasket = new ObservableCollection<Fruit>(FruitBasket);
//Reselect
SelectedFruit = oldSelectedItem;
}
public bool CanExecute()
{
return true; //for now
}
Now this does work, but I have some problems with it:
First, I feel like there are a lot of conditions that have to come together for this to work and I wonder if I'll get so lucky trying to move some Telerik Drag and Drop code into MVVM.
Second, it seems like a pretty poor performance approach to recreate the list like that.
Lastly, it seems like this would be easier in code behind (though I'm not 100% certain I still won't have to rebuild that list).
Does anyone have any thoughts on my approach or perhaps even... suggestions to make things easier? Am I just missing something obvious here?
Thanks
-Driodilate :]
maulkye,
There is something going wrong if you have to refresh your ObservableCollection. Usually, you should not need it because the ObservableCollection will notify about item changes.
Never do this:
FruitBasket = new ObservableCollection<Fruit>(FruitBasket);
Your public ObservableCollection<Fruit> FruitBasket should have no public setter, it should be read only. Just Add or Remove Items to/from the list.
If you want to handle multiple selections, you will probably need an extended CollectionView which can handle this, get more hints here.
I hope this helps a little bit, even if I probably didn't answer all questions : )
EDIT:
Ok, I guess i got some things wrong. Now i guess i fully understand what you're trying to accomplish. You are not getting notified when your property is changed, right? Well, for this reason, we've adapted "BindableLinq" in one of our projects, which you can compile in Silverlight without problems. (there are similar solutions available, called Continuous Linq or Obtics, make your choice).
Using BindableLinq, you can transform your ObservableCollection to a BindableCollection using one single extension method. The BindableCollection will then reflect all changes properly. Give it a try.
EDIT2:
To implement a proper ViewModel, Please consider the following Changes.
1) Fruit is your Model. Since it doesn't implement INotifyPropertyChanged, it won't propagate any changes. Create a FruitViewModel, embedding your Fruit Model and invoke RaisePropertyChanged for each property setter.
2) Change your FruitBasket to be an ObservableCollection of FruitViewModel. Slowly it starts to make sense :)
3) SelectedFruit has to be a FruitViewModel as well. Now it makes even more sense.
4) Now it already works for me, even without BindableLinq. Did you have any success?
HTH
best regards,
thomas