View inside a View and data synchronization - wpf

I am having one view where I need to display some Grid and TabControl. There is one column on a grid that should display something like a Note (Remark) property. Since this field can contain large amount of data, I am going to have one tab with TextBox control that should allow user to see/edit note, while grid column will show only a few first letters on the note.
I am going to post relevant parts only:
public classSomeViewModel : ViewModelBase
{
public SomeViewModel()
{
TabScreens = New List<ViewModelBase>();
TabScreens.Add(new AnotherViewModel1());
TabScreens.Add(new AnotherViewModel2());
}
List<ViewModelBase> TabScreens{get;set;}
}
SomeView xaml:
<DataTemplate DataType="{x:Type vm:AnotherViewModel1}">
<vw:AnotherView1 />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AnotherViewModel2}">
<vw:AnotherView2 />
</DataTemplate>
AnotherView2:
<Grid>
<TextBox Text={Binding Note} />
</Grid>
AnotherViewModel2:
public class AnotherViewModel2
{
public string Note {get;set;}
}
}
So TabControl on View is bound to TabScreens. DataTemplates ensure that both AnotherView1 and AnotherView2 will be loaded when SomeView is loaded. Each row in grid contains different remark. What is the cleanest way to synchronize SomeViewModel Remark and AnotherViewModel2 Remark?

Combine the 2 view models into a master view model with a single Remark property. Don't introduce a need to synchronize if it's not necessary.

Related

Switching view/view model based on a parameter

In a wpf application I've to show different filter user control based on a parameter passed to the main view model. Each filter's view model implements IReportFilter and the main view model has a property of type
IReportFilter Filters {get;set;}
How do I resolve the correct xaml view?
Thanks in advance
Assuming that you have a base class instead of IReportFilter,say ReportFilter.
Follow the below steps:
1. Define DataTemplate For Filter1VM & Filter2VM & set the usercontrols.
Note that I have naming conventions while defining the control & VMs.
<DataTemplate DataType={x:type viewModels:Filter1VM}>
<usercontrols:Filter1/>
</DataTemplate>
<DataTemplate DataType={x:type viewModels:Filter2VM}>
<usercontrols:Filter2/>
</DataTemplate>
2. You need to define a custom DataTemplateSelector.
class CustomDataTemplateSelector:DataTemplateSelector
{
public ovverride DataTemplate SelectTemplate(object item,....)
{
Type t=item.GetType();
string typename=t.Name;
string viewName=typeName.Replace("VM",String.Empty);
DataTemplate dt=App.Current.Resources[viewname] as DataTemplate;
return dt;
}
}
3.Define a property TemplateSelector in ReportFilter class & initialise it in constructor as:
TemplateSelector=new CustomDataTemplateSelector();
4. In your window's VM, create ReportFilter Filter property:
ReportFilter Filter {get;set;}
In your application Window,add ContentControl where you need to place the filterControl:
ContentControl Content="{Binding Filter}"
ContentTemplateSelector="{Binding Filter.TemplateSelector}"
In your window's view model, assing Filter as Filter1VM/Filter2VM based on the parameter passed.
Try the following
<ContentControl Content="{Binding SomeContent}"/>
Then in viewmodel create two controls for your needs and simply switch them based on your filter
UPDATE
If you don't want to create controls in ViewModel then you can do the following for example:
<Grid>
<Column / Rows Definitions>
<YourControl1 Grid.Row="X" Grid.Column="Y" IsVisible={Binding BoolFilter1, Converter={StaticResource Bool2Visibility}/>
<YourControl1 Grid.Row="X" Grid.Column="Y" IsVisible={Binding BoolFilter2, Converter={StaticResource Bool2Visibility}/>
....
</Grid>

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}">
.............

Adding controls dynamically according to data type WPF

I need to create a childwindow that should contain the controls according to the model passed to it. For Example, If a model contains 5 properties (it can contain any number of properties), 2 of type string, 1 datetime and 2 lists, it should create 2 textboxes with labels as property name, 1 Datepicker with lable as property name, and 2 comboboxes with label as property name. Basically, controls should be created dynamically according to properties along with the Label as name of the property. I am following MVVM. Any help will be appreciated. Thanks.
Get the list of PropertyInfos of your model and wrap them in ViewModels. Then use DataTemplates with implicit keys to generate your controls.
Step1: Get PropertyInfoViewModels
var vms = model.GetType().GetAllProperties.Select(p=> ViewModelFactory.Create(p));
Your factory should return a StringPropertyViewModel for string properties, etc.
abstract class PropertyViewModel<T> : INotifyPropertyChanged
{
public string Caption {get; set;}
public T Value {get; set;}
}
Step2: DataTemplates
<DataTemplate TargetType="{x:Type sys:StringPropertyViewModel}">
<StackPanel Orientation="Horizontal">
<Label Text="{Binding Caption}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
Step3:
Ensure the DataTemplates are in the Resources section of your Window or can be resolved via a ResourceDictionary.
Step4:
In the window ViewModel, expose the generated PropertyViewModels
<Window...>
...
<ItemsControl ItemsSource="{Binding ModelPropertyViewModels}">
<ItemsControl.Resources>
<!-- This might be a good place to post your DataTemplates --->
<ItemsControl.Resources>
</ItemsControl>
...
</Window>

How to get data into the ViewModel of a UserControl?

New to WPF/MVVM. I have a data object of type "MyData". One of its properties is of type "MySubsetData".
I show a collection of "MyData" objects in a datagrid.
<DataGrid ItemsSource="{Binding Path=MyDataCollection}">
<!-- Each row of the datagrid contains an item of type "MyData" -->
<DataGrid.Columns .../>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<local:MySubsetDataUserControl/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The row details should show the content of "MySubsetData". The view of the row details is in a separate user control (here: "MySubsetDataUserControl").
At the moment I don't set a view model for "MySubsetDataUserControl", so it inherits the data context from the parent's datagrid row.
<UserControl>
<!-- Namespace stuff not shown for simplicity -->
<Grid DataContext="{Binding Path=MySubsetData}">
<!-- Show the MySubsetData properties here -->
<!-- e.g. a textbox -->
<TextBox Text="{Binding Path=TextData, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
Altough this is working I face several problems with this approach:
All business logic will be in the user control parent's view model, where it simply doesn't belong. Making the view model messier than it need to be. Not to mention that command bindings in the user controls xaml look very ugly as well. It just doesn't feel right.
As more row details could be visible at the same time, I can't bind the properties of "MySubsetData" to an observable property in the view model. I.e. if I change a property in code (e.g. TextData) the change will not be reflected in the view. My workaround is not to alter the property "TextData". Instead I change the content of the textbox Text property, which in turn will update the "TextData" property. And that feels very wrong!
So I would like to use another view model for my user control, but I don't know how to access my data then.
<UserControl.DataContext>
<local:UserControlViewModel/>
</UserControl.DataContext>
How do I access "MySubsetData" now?
Let assume you have a view model like this:
public class ViewModel
{
public IEnumerable<MyData> MyDataCollection{get; private set;}
}
Where
public class MyData
{
public MySubsetData MySubsetData { get; }
}
Your view containing the DataGrid would be
<UserControl.DataContext>
<local:ViewModel>
</UserControl.DataContext>
<DataGrid ItemsSource="{Binding Path=MyDataCollection}">
<DataGrid.Columns .../>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<!-- each row of the items control has an implicit DataContext of MyData -->
<!-- so bind the DataContext of the subset control to MySubsetData -->
<local:MySubsetDataUserControl DataContext={Binding MySubsetData}/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Now your subset control can look like
<UserControl>
<Grid>
<TextBox Text="{Binding Path=TextData, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
On re-reading the question, maybe all I've done is repeat the Xaml from the question in a slightly different way.
However the various classes should be implementing INotifyPropertyChanged, e.g.
public class MySubsetData : INotifyPropertyChanged
{
public string TextData
{
get{...}
set{...; OnPropertyChanged("TextData"); }
}
}
Then the TextBox bound to the TextData property will reflect changes made in code.

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

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>

Resources