In my app I have a lot of ComboBox with item list predefined by users. I do not want add all lists on my ViewModel (maybe I'm wrong). I prefer to add an additional parameter to the MyComboBox control (new control which inherit from the ComboBox) with the id of list which I want load from database. E.g.:
<MyComboBox ItemsSourceId = "SAMPLE_ID" SelectedItem = "{Binding valueCode}" />
In behind code I will execute query and bind results to ItemSource.
SELECT itemCode, itemValue FROM UserDictionaries WHERE itemListCode = 'SAMPLE_ID'
It is good or bad idea? Maybe you have some sample code? ;)
Advantages of the solution: cleaner ViewModel. Disadvantages: database context in control.
Why don't you want to put it in the ViewModel? This is exactly what the ViewModel is for. If you were to have it on the code-behind of your custom ComboBox, this will defeat the purpose of MVVM, as the ComboBox is now dependent on the data-base. What you should strive for is to have 'loosely coupled' components, and let the ViewModel feed the data to the View.
You can do something like:
public class ViewModel
{
private _itemSource;
public List<Entity> ItemSource
{
get { return _itemSource; }
private set
{
_itemSource = value;
RaisePropertyChanged("ItemSource");
}
}
private void UpdateItemSource(int sampleId)
{
var newItems = SELECT itemCode, itemValue FROM UserDictionaries WHERE itemListCode = 'SAMPLE_ID';
ItemSource = null;
ItemSource = newItems;
}
}
and then update your XAML to:
<ComboBox ItemSource="{Binding ItemSource, UpdateSourceTrigger=PropertyChanged}" SelectedItem = "{Binding valueCode}" />
Related
This is my first MVVM project, I hope can be clear.
Having this in Model:
public class Category
{
public int CategoryId { get; set; }
public string Description { get; set; }
}
In ViewModel:
public class CategoryViewModel : MyViewModelBase
{
private ObservableCollection<Category> categories;
public ObservableCollection<Category> Categories
{
get { return categories; }
set
{
categories = value;
NotifyPropertyChanged(nameof(Categories));
}
}
}
In View (XAML)
The items are bound to a ComboBox:
<ComboBox x:Name="cboCategories"
HorizontalAlignment="Left
VerticalAlignment="Top"
Width="250"
IsEditable="True"
ItemsSource="{Binding Categories}"
SelectedValuePath="CategoryId"
DisplayMemberPath="Description" />
Is there a way to add a new item (Category) to the ObservableCollection property when the user writes a new entry on the control?
I've been able to do it by showing up a little Window with a TextBox, but I'd like to know if is possible to short this process.
I'm not really familiar with WPF, any help would be appreciated.
Say you had one collection of Category and that is bound to the itemssource of your combo.
You then bind selecteditem to a property of type Category with a propfull so you have setter where you can put code.
When that setter fires you get a selected Category.
You could then do what you like with it.
One option would be to add it to another observablecollection.
The pattern of acting when you select an item off a list is described here:
https://social.technet.microsoft.com/wiki/contents/articles/30564.wpf-uneventful-mvvm.aspx#Select_From_List_IndexChanged
In that you would add the chef to another observablecollection in DoSomethingWhenChefChanged.
You could handle the TextBoxBase.TextChanged attached event and for example raise a command of the view model or add the item to the ObservableCollection directly, e.g.:
private void cboCategories_TextChanged(object sender, TextChangedEventArgs e)
{
var cmb = sender as ComboBox;
var viewModel = DataContext as CategoryViewModel;
if (viewModel != null)
{
viewModel.Categories.Add(new Category() { Description = cmb.Text });
}
}
XAML:
<ComboBox x:Name="cboCategories"
IsEditable="True"
TextBoxBase.TextChanged="cboCategories_TextChanged" ... />
If you want to invoke the attached command using an interaction trigger, you could create your own custom EventTrigger as suggested here:
http://joyfulwpf.blogspot.se/2009/05/mvvm-invoking-command-on-attached-event.html
https://social.msdn.microsoft.com/Forums/vstudio/en-US/c3e9fad4-16ee-4744-8b0e-1ea0abfc5da7/how-to-handle-scrollviewerscrollchanged-event-in-mvvm?forum=wpf
I have two user controls, one contains a TreeView, one contains a ListView.
The TreeView has an itemsource and hierarchical data templates that fill the nodes and leafes (node=TvShow, leaf=Season).
The ListView should show the children of the selected TreeView item (thus, the selected season): the episodes of that season.
This worked fine when I had both the TreeView and the Listview defined in the same window, I could use something like this:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
How can I achieve this, when both controls are defined in separate user controls? (because in the context of one user control, I miss the context of the other user control)
This seems something pretty basic and I am getting frustrated that I can't figure it out by myself. I refuse to solve this with code-behind, I have a very clean MVVM project so far and I would like to keep it that way.
Hope that somebody can give me some advise!
First of all you have to created the SelectedValue proeprty in your ViewModel and bind the TreeView.SelectedItem property to it. Since the SelectedItem property is read-only I suggest you to create a helper to create OneWayToSource-like binding. The code should be like the following:
public class BindingWrapper {
public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }
public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));
static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SetTarget(d, e.NewValue);
}
}
The idea is simple: you have two attached properties, the Source and the Target. When the first one changes the PropertyChangedCallback is called and you simply setting the NewValue as the Target property value. In my opinion this scenario is helpful in a lot of cases when you need to bind the read-only property in XAML (especially in control templates).
I've created a simple model to demonstrate how to use this helper:
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.values = new ObservableCollection<string>()
{
"first",
"second",
"third"
};
}
ObservableCollection<string> values;
string selectedValue;
public ObservableCollection<string> Values { get { return values; } }
public string SelectedValue {
get { return selectedValue; }
set {
if (Equals(selectedValue, values))
return;
selectedValue = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, we have data source, selected value and we'll bind it like this:
<StackPanel>
<TreeView ItemsSource="{Binding Values}"
local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>
In the TreeView bound to the ItemsSource from the ViewModel I've created two bindings so they are changing the SelectedValue property in your ViewModel. TextBlock in the end of the sample is used just to show that this approach works.
About the very clean MVVM - I think that it is not the same as the "no code-behind". In my sample the ViewModel still doesn't know anything about your view and if you'll use another control to show your data e.g. ListBox you will be able to use the simple two-way binding and the "BindingWrapper" helper will not make your code unreadable or unportable or anything else.
Create a SelectedSeason property in your ViewModel and bind the ListView's ItemsSource to SelectedSeason.Episodes.
In a perfect world, you could now use a Two-Way binding in the TreeView to automatically update this property when the SelectedItem changes. However, the TreeView's SelectedItem property is readonly and cannot be bound. You can use just a little bit of code-behind and create an event handler for the SelectionChanged event of the TreeView to update your ViewModel's SelectedSeason there. IMHO this doesn't violate the the MVVM principles.
If you want a pure XAML solution, that a look at this answer.
I have a simple combobox with a checkbox inside as such:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="158,180,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding collection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}"></CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The datacontext is simply the code behind, and to test it I use the following code:
public ObservableCollection<Foo> collection { get; set; }
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
}
When I set the ItemsSource as I have in the code, then it works fine, but I want to set the ItemsSource in the Xaml, however it does not work using the Xaml above. I have also tried setting it to Path = "". Anybody know why?
Thanks
You need to assign DataContext to the control. something like:
var window = new Window1();
window.DataContext = new WindowDC();
window.Show();
where Window1 class contains the combobox, and WindowDC is like:
public class WindowDC
{
public ObservableCollection<Foo> collection { get; set; }
}
That's how this will work.
What you actually do is that you place collection into control class, and set your datacontext for combobox only.
But, for testing purposes, you can still set Combox.Datacontext in control constuctor.
Bindings in WPF always have a Source. If you don't specify the source in the binding itself, then it will implicitly use the DataContext of the control or an ancestor of it. So if you want to bind to properties in your codebehind file, you have to set the DataContext to an object of the class which contains the collection property. In your case this is the instance of the Window (this).
DataContext = this;
As the commentor pointed out, it's not considered good style putting business logic or data inside the code behind file. So consider writing a separate class which contains your collection property and which you can use to initalize your DataContext. If you are writting bigger applications you should take a look at patterns like MVVM, which uses databinding to provide a better separation between your view and your model.
Edit: Changed ordering and incorporated feedback
Make sure there exist a public property collection in your code behind.
in the code behind also do this.DataContext = this
Finally implement INotifyPropertyChanged to tell the view that you have changed the collection once you add items in it
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
//this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
OnPropertyChanged("Collection");
}
It is working when you are setting combo's item source in code behind because the source of combo is getting updated like wise to set the item source in XAML you have to make a property with INotifyPropertyChanged that keep update the combo's itemsource every time you update your collection via this property..
private ObservableCollection<Foo> _Collection;
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
Now as you are filling collection on button click you just have to set that collection in the property as..
_Collection = new ObservableCollection<Foo>();
Foo f = new Foo("DSD");
_Collection .Add(f);
Collection = _Collection ; //here property call OnPropertyChange
like wise you can provide data to any control. It is jsut the game of INotifyPropertyChanged property.
Hope this will help you
I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.
I have a combobox and a listbox in a WPF window.
The combobox's itemssource is set to a List of all Team objects. Team has 2 properties (TeamId and TeamName).
The listbox's itemssource is set to a List of all Player objects. Player on of Players properties is TeamId.
I would like to filter the list of Players in the Listbox to only show those Players whose TeamId matches the TeamId of the SelectedItem in my combobox.
I would prefer to do this all in XAML but I'm not really sure on what the correct way to do it in C# would be either. Any help would be appreciated.
I'm not sure you can do it entirely in xaml, i think you might need a tiny bit of work somewhere else. This is how i did it for something else.
Wrap your collection with a CollectionViewSource in your xaml (this makes one that has a sort on a specific property name):
<CollectionViewSource x:Key="ViewName" Source="{Binding YourBinding}">
<CollectionViewSource.SortDescriptions>
<comp:SortDescription PropertyName="Name" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
somewhere else, bind your listview to have this source as the itemssource:
<ListView x:Name="MyList" ItemsSource="{Binding Source={StaticResource ViewName}}" />
then somewhere in code, i have mine on a textbox property change listener, but you get the general idea. the ICollectionView interface has a filter member that you can use to filter things out.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var text = FilterTextBox.Text;
var source = MyList.Items as ICollectionView;
if (string.IsNullOrWhiteSpace(filter))
{
source.Filter = null;
}
else
{
source.Filter = delegate(object item)
{
var s = item as INamedItem;
return s.Name.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) != -1;
};
}
}
Firstly, change all your bound collections to ObservableCollection.
Then, on the combobox, bind the SelectedValue to another property on your DataContext of type Team (you have implemented INotifyPropertyChanged right?).
When the SelectedValue changes, refresh the ListBox's bound collection with a filtered list from the collection of all players:
public ObservableCollection<Team> Teams { get;set;}
public ObservableCollection<Player> Players { get;set;}
private List<Player> AllPlayers {get;set}
public Team CurrentTeam
{
get
{
return this._currentTeam;
}
set
{
this._currentTeam = value;
this.Players = new ObservableCollection(this.AllPlayers.Where(x => x.TeamId = this._currentTeam.TeamId));
RaisePropertyChanged("CurrentTeam");
}
}
This is the quickest and easist way to do it. You could probably achieve this through CollectionView, but I think this is simpler to understand.