How to bind an observable collection to Multiple user controls at runtime? - wpf

I am stucked at the part where I have to bind a collection to a dynamic usercontrol. Scenario is something like this.
I have a dynamic control, having a expander , datagrid, combobox and textbox, where combox and textbox are inside datagrid. There are already two collections with them. One is binded with combobox and another is binded with datagrid. When the item is changes in combox its respective value is set to its respective textbox, and so on. and this pair of value is then set to the collection binded with datagrid. A user can add multiple items.
Now the main problem is that all these things are happening inside a user control which is added dynamically, that is on button click event. A user can add desired numbers of user controls to the form.
problem is coming in this situtaion. Say I have added 3 controls. Now in 1st one if i add a code to the collection then it gets reflected in the next two controls too, as they are binded with same collection.
So, I want to know is there anyway to regenrate/rename the same collection so that the above condition should not arise.

It's hard to answer your question without seeing the bigger picture, however I have a feeling you are going about this the wrong way. It appears that you are adding instances of your user control directly from code. Instead of doing that, you should create some kind of ItemsControl in your XAML, and in its ItemTemplate have your user control. Bind that ItemsControl to a collection in your view model, and only manipulate that collection.
You should not be referring to visual controls in your view model or code behind. Whenever you find yourself referencing visual elements directly from code, it should raise a warning flag in your mind "Hey! There's a better way than that!"...
Example:
The view model:
public class ViewModel
{
public ObservableCollection<MyDataObject> MyDataObjects { get; set; }
public ViewModel()
{
MyDataObjects = new ObservableCollection<MyDataObject>
{
new MyDataObject { Name="Name1", Value="Value1" },
new MyDataObject { Name="Name2", Value="Value2" }
};
}
}
public class MyDataObject
{
public string Name { get; set; }
public string Value { get; set; }
}
The window XAML fragment containing the list box and the data template:
<Window.Resources>
...
<DataTemplate x:Key="MyDataTemplate">
<local:MyUserControl/>
</DataTemplate>
</Window.Resources>
...
<ListBox ItemsSource="{Binding MyDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}"
HorizontalContentAlignment="Stretch"/>
The user control:
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}" HorizontalAlignment="Right"/>
</UniformGrid>

Related

Combo box with submenu

How can I show the combo box like this one, in C#.net 2010
It is possible by combining two controls ComboBox and TreeView in one UserControl.
Although this control looks quite simple, the actual implementation isn’t clear and takes long time.
Here is the sequence of steps:
1) Custom TreeView and TreeViewItem. They provide the following functionality:
Allow to expand and select an item from a view model (It isn’t possible to select an item of the TreeView using other ways)
The event which is fired when a user clicks a TreeViewItem (so it will be possible to close the ComboBox)
2) Interface for an item of the ItemsSource collection
public interface ITreeViewItemModel
{
string SelectedValuePath { get; }
string DisplayValuePath { get; }
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
IEnumerable<ITreeViewItemModel> GetHierarchy();
IEnumerable<ITreeViewItemModel> GetChildren();
}
Members of this interface:
IsExpanded – allows to expand the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
IsSelected – allows to select the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
SelectedValuePath – the unique string (unique item id) that will be used to select and expand the treeview control.
DisplayValuePath – the content that will be displayed at the header when the combobox is closed
GetHierarchy – returns the item and its parents.
GetChildren – returns child items of the current item
Create the Combobox:
It is the most difficult part of the implementation. I was forced to create many methods to provide the connection between Combobox and TreeView.
But although there is many private methods, there is only two public properties:
SelectedItem – now it is possible to get or set this item and it will be selected in the treeview.
SelectedHierarchy – it wasn’t necessary to create this property, but it wasn’t difficult so I’ve decided to implement it. Use list of strings instead of the actual item.
Add it to a view and bind with a view model:
<UserControl.Resources>
<Windows:HierarchicalDataTemplate x:Key="treeViewDataTemplate"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Title}" />
</Windows:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<local:ComboBoxTreeView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
ItemTemplate="{StaticResource treeViewDataTemplate}"
HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>
The ItemTemplate property is obligatory property and must be of the HierarchicalDataTemplate type.
Check this out https://vortexwolf.wordpress.com/2011/04/29/silverlight-combobox-with-treeview-inside/

Setting separate data binding for controls in User Control

I am relatively new to WPF and I am attempting to design my first project using MVVM pattern.
Now I have the main window as my view.I have an user control on this.Now the user control has 2 expanders and each need to pull it's own data.
<Expander Header="Electrical Components" Name="EC"
IsExpanded="True">
<ItemsControl ItemsSource="{Binding ElecViewModel.ToolBoxItems }">
............
<Expander Header="Structural Components" Name="SC"
IsExpanded="True">
<ItemsControl ItemsSource="{Binding StructViewModel.ToolBoxItemsS}">
.............
So I created 2 view models with the idea that I can do a data bind for each of the expanders. The idea is to pull some images under each expander.
First expander I attach to this view model
public class ElecViewModel
{
private List<ToolBoxData> toolBoxItems = new List<ToolBoxData>();
public ElecViewModel()
{
toolBoxItems.Add(new ToolBoxData("../Images/Inverter.jpg", typeof(InverterDesignerItemViewModel)));
toolBoxItems.Add(new ToolBoxData("../Images/Recombiner.jpg", typeof(RecombinerDesignerItemViewModel)));
}
public List<ToolBoxData> ToolBoxItems
{
get { return toolBoxItems; }
}
}
..And second one to this view model
public class StructViewModel
{
private List<ToolBoxData> toolBoxItemsS = new List<ToolBoxData>();
public StructViewModel()
{
toolBoxItemsS.Add(new ToolBoxData("../Images/SafetySwitch.jpg", typeof(SafetySwitchDesignerItemViewModel)));
toolBoxItemsS.Add(new ToolBoxData("../Images/ScadaPanel.jpg", typeof(ScadaDesignerItemViewModel)));
}
public List<ToolBoxData> ToolBoxItemsS
{
get { return toolBoxItemsS; }
}
}
Now my first expander is getting loaded with the correct images.whereas the second one doesn't. The public list of the second view model does not get hit even though I have binded to it like the first list ? Is there an obvious reason for that ?I tried setting the binding source of second expander to first list and it gets populated suggesting the list I am creating for second one has some issue in getting binded to a control. Please suggest as i am not able to identify any obvious issue.
In your condition don't set the DataContext of your usercontrol explicitly. Assuming your MainViewModel has two properties for ViewModel instances ElecViewModel and StructViewModel. And MainViewModel is DataContext of your Window, you can bind these expanders like
<Expander Header="Electrical Components" Name="EC"
IsExpanded="True">
<ItemsControl ItemsSource="{Binding ElecViewModel.ECData}">
............
<Expander Header="Structural Components" Name="SC"
IsExpanded="True">
<ItemsControl ItemsSource="{Binding StructViewModel.SCData}">
.............

ItemsControl with databound ItemsSource and two-way binding of elements (replacable items)

I have an ObservableCollection of objects (e.g. Persons with First/Last Name) which I would like to display in an ItemsControl. Each Item is displayed in a custom "editor" control, which allows editing of the object's properties.
This part is working fine and fairly standard.
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<custom:PersonEditor Person="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
However, the custom editor control also has the ability to replace the entire object is has received (rather than just editing a person's name, replace it with a new person object).
What I am looking for is a way to push this change back into the ObservableCollection. As it is now, changing the Person object within the editor does not replace the item in the list, which would be the desired outcome.
Any help would be appreciated.
If you don't have access to the custom control itself what you could try would be using a setter on a property to clear the ObservableCollection and re-add the items instead of outright replacing it.
For example:
private ObservableCollection<MyModel> _dataSource;
public ObservableCollection<MyModel> DataSource
{
get
{
return _dataSource;
}
set
{
_dataSource.Clear();
foreach(var item in value)
{
_dataSource.Add(item);
}
}
}
This would prevent the item itself from getting replaced which causes issues with ItemSources since they apparently only bind to the items property once.

WPF tab control and MVVM selection

I have a TabControl in an MVVM WPF application. It is defined as follows.
<TabControl Style="{StaticResource PortfolioSelectionTabControl}" SelectedItem="{Binding SelectedParameterTab}" >
<TabItem Header="Trades" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailTradeRegion" cal:RegionManager.RegionName="NSDetailTradeRegion" />
</TabItem>
<TabItem Header="Ccy Rates" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailCcyRegion" cal:RegionManager.RegionName="NSDetailCcyRegion" />
</TabItem>
<TabItem Header="Correlations / Shocks" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Name="NSDetailCorrelationRegion" cal:RegionManager.RegionName="NSDetailCorrelationRegion" />
</TabItem>
<TabItem Header="Facility Overrides" Style="{StaticResource PortfolioSelectionTabItem}" IsEnabled="False">
<ContentControl Name="NSDetailFacilityOverrides" cal:RegionManager.RegionName="NSDetailFacilityOverrides" />
</TabItem>
</TabControl>
So each tab item content has its own view associated with it. Each of those views has the MEF [Export] attribute and is associated with the relevant region through view discovery, so the above code is all I need to have the tab control load and switch between them. They all reference the same shared ViewModel object behind them and so all interact seamlessly.
My problem is that when the user navigates to the parent window, I want the tab control to default to the second tab item. That is easy enough to do when the window is first loaded, by specifying in XAML IsSelected="True" in TabItem number 2. It is less easy to do when the user navigates away from the screen and then comes back to it.
I thought about having a SelectedItem={Binding SelectedTabItem} property on the tab control, so I could programmatically set the selected tab in the ViewModel, but the problem is I have no knowledge of the TabItem objects in the ViewModel as they are declared above in the XAML only, so I have no TabItem object to pass to the setter property.
One idea I had was to make the child Views (that form the content of each of the tab items above) have a style on the UserControl level of their XAML, something along the following.
<Style TargetType={x:Type UserControl}>
<Style.Triggers>
<DataTrigger Property="IsSelected" Value="True">
<Setter Property="{ElementName={FindAncestor, Parent, typeof(TabItem)}, Path=IsSelected", Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
I know the findancestor bit isn't correct; I've just put it there to specify my intent, but I am not sure of the exact syntax. Basically for each UserControl to have a trigger that listens to a property on the ViewModel (not sure how I would distinguish each different UserControl as obviously they can't all listen to the same property or they would all select simultaneously when the property is set to True, but having a property for each usercontrol seems ugly) and then finds its parent TabItem container and sets the IsSelected value to true.
Am I on the right track with a solution here? Is it possible to do what I am pondering? Is there a tidier solution?
If you look at the TabControl Class page on MSDN, you'll find a property called SelectedIndex which is an int. Therefore, simply add an int property into your view model and Bind it to the TabControl.SelectedIndex property and then you can select whichever tab you like at any time from the view model:
<TabControl SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
UPDATE >>>
Setting a 'startup' tab is even easier using this method:
In view model:
private int selectedIndex = 2; // Set the field to whichever tab you want to start on
public int SelectedIndex { get; set; } // Implement INotifyPropertyChanged here
Just FYI,
I gone through the same issue where I add tabs dynamically using ObservableCollection source but last added Tab do not get selected.
I have done same changes what Sheridan said to select Tab as per SelectedIndex. Now last added Tab gets selected but it was not getting focused.
So to focus the Tab we have to add set Binding IsAsync property True.
<TabControl ItemsSource="{Binding Workspaces}" Margin="5" SelectedIndex="{Binding TabIndex, Mode=OneWay,UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
The below code sample will create a dynamic tab using MVVM.
XAML
<TabControl Margin="20" x:Name="tabCategory"
ItemsSource="{Binding tabCategory}"
SelectedItem="{Binding SelectedCategory}">
<TabControl.ItemTemplate>
<DataTemplate>
<HeaderedContentControl Header="{Binding TabHeader}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding TabContent}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Modal Class
TabCategoryItem represents each tab item. On two properties, TabHeader will display a tab caption and TabContent contains the content/control to fill in each tab.
Public Class TabCategoryItem
Public Property TabHeader As String
Public Property TabContent As UIElement
End Class
VM Class
Public Class vmClass
Public Property tabCategory As ObjectModel.ObservableCollection(Of TabCategoryItem)
Public Property SelectedCategory As TabCategoryItem
End Class
The below code will fill and bind the content. I am creating two tabs, tab1 and tab2. Both tabs will contain text boxes. You can use any UIelement instead of text boxes.
Dim vm As New vmClass
vm.tabCategory = New ObjectModel.ObservableCollection(Of TabCategoryItem)
'VM.tabCategory colection will create all tabs
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab1", .TabContent = new TextBlock().Text = "My first Tab control1"})
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab2", .TabContent = new TextBlock().Text = "My first Tab control2"})
mywindow.DataContent = vm
The accepted answer is not working with DependencyObject on your ViewModel .
I'm using MVVM with DependencyObject and Just setting the TabControl didn't work for me.The problem I had was the the property was not getting update on the View when I was setting the tab selectedIndex from the ViewModel.
I did set the Mode to be two ways but nothing was working.
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
The ViewModel property "SelectedTab" was getting updated all the time when I navigated between tabs. This was confirming my binding was working properly. Each time I would navigate the tabs both the Get and Set would get called in my ViewModel. But if I try to set the SelectedIndex in the ViewModel it would not update the view.
ie: SelectedTab=0 or SelectedTab=1 etc...
When doing the set from the ViewModel the SelectedTab 'set' method would be called, but the view would never do the 'get'.
All I could find online was example using INotifyPropertyChanged but I do not wish to use that with my ViewModel.
I found the solutions in this page: http://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged
With DependencyObject, you need to register the DependencyProperties. Not for all properties but I guess for a tabcontrol property you need to.
Below my code:
view.xaml
//Not sure below if I need to mention the TwoWay mode
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
ViewModel.cs
public class ViewModel : DependencyObject
{
public static readonly DependencyProperty SelectedTabDP = DependencyProperty.Register("SelectedTab", typeof(int), typeof(ViewModel));
public int SelectedTab
{
get { return (int)GetValue(SelectedTabDP); }
set { SetValue(SelectedTabDP, value); }
}
}
Basically all I had to do was to actually register the dependency property (DependencyProperty) as you can see above.
What made this hard to figure out was that I have a bunch of other Properties on that view and I didn't need to register them like that to make it work two ways. For some reason on the TabControl I had to register the property like I did above.
Hope this help someone else.
Turns out my problem were because my components have names:
x:Name="xxxxxxxx"
Giving names to components at the same time of biding them with DependencyObject seems to be the main cause of all my issues.
In order to improve semantic of my viewmodel and to not work with an int when using code to check for the selected tab, I made some additions to the accepted answer so to use an Enum instead of an int.
These are the steps:
Define an Enum representing the different tabs:
public enum RulesVisibilityMode {
Active,
History
}
Expose the SelectedTab as a property using the enum instead of the int:
public RulesVisibilityMode SelectedTab { get; set; }
Create a converter to convert from an int to your enum (I don't need the ConvertBack because I never select the active tab from the code, but you can add it too):
internal class RulesVisibilityModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("Conversion from visibility mode to selected index has not been implemented");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int selectedTabIndex;
if (int.TryParse(value.ToString(), out selectedTabIndex))
{
return (RulesVisibilityMode)selectedTabIndex;
}
return null;
}
}
Bind the tabcontrol to the SelectedTab property through the converter:
<TabControl SelectedIndex="{Binding SelectedTab, Mode=OneWayToSource, Converter={StaticResource RulesVisibilityModeConverter}}" ...
Now every time you need to check for the selected tab in the code you deal with a readable enum:
if (this.SelectedTab != RulesVisibilityMode.Active) ...

Make only one of many controls visible based on value of another control

I have a ComboBox bound to a collection of objects defined as this.
public class TierOption
{
public string Option { get; set; }
public Type DataType { get; set; }
}
public class TierOptions : ObservableCollection<Tier1Option>
{
}
I have 3 other controls related to this ComboBox, which are a TextBox, ComboBox, or a WPFToolKit:DatePicker.
I need to show only the related control which corresponds to the datatype(Type) of the object selected in the first ComboBox and neither of the others.
Pseudo Code Example:
(Probably too close to butchered C# but hopefully it conveys the idea)
switch (ComboBox.SelectedItem.DataType)
{
case String:
TextBox.Visibility = Visibility.Visible;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Hidden;
break;
case DateTime:
TextBox.Visibility = Visibility.Hidden;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Visible;
break;
<...so forth and so on...>
}
My attempts have resulted in very non-wpf looking convoluted messes which don't work regardless. Being new to wpf I'm trying very hard to stay true to the best design practices.
Thank you!
You can play with DataTemplate with DataType property
<...Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type DateTime}">
<DatePicker .../>
</DataTemplate>
...
</...Resources>
<ContentControl Content="{Binding SelectedItem, ElementName=myComboBox}"/>
<ComboBox ItemsSource="{Binding ...}"/>
The code above is just the idea, you could have to make adjustements. For example you won't be able to modify a string item itself (you could have to encapsulate each item of your list)
If your list contains all items of the same type, you can use a ContentTemplateSelector on contentControl.
ContentControl Content="{Binding SelectedItem, ElementName=YourCombBox}" ContentTemplateSelector="{StaticResource YourTemplateSelector}"
MSDN DOC about ContentControl.ContentTemplateSelector Property
Bind to the detail visibility to ElementName=ComboBox Path=SelectedItem.DataType. And you will need to use a converter that returns visibility. You will need two converters return opposite answers. If you have more than 2 combination then some more in the line of Jonas.
I assumed Type was a system class and it appears to be a custom class. Extend that class to have additional properties. Even if Type was a system type you could just create a class that implements it and extend it.
public Visibility TextBoxVisibility { get; }
public Visibility ComboBoxVisibility { get; }
...
Then on TextBox bind the visibility
Visisbility="{binding ElementName=Combobox Path=SelectedItem.DataType.TextBoxVisibility]";

Resources