Best practice adding UserControl to parent control via binding - c#

I have an ObservableCollection<ControlMachine> where each ControlMachine is a UserControl used to display information about one computer.
public static ObservableCollection<ControlMachine> ListControleMachine =
new ObservableCollection<ControlMachine>();
I've tried to display the collection using binding
<Grid ForceCursor="True" x:Name="grid2" OpacityMask="{x:Null}" Margin="10,38,10,10" Background="#FFF5F6F6">
<ItemsControl ItemsSource="{Binding ListControleMachine}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type Brush}">
<Rectangle Canvas.ZIndex="3" Width="100" Height="100" Canvas.Top="100" Canvas.Left="100" Fill="blue"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
My user control is not showing in the Grid with this code. What's wrong?
Question: What is the best practice for dispose such ControlMachine one under the others?.
Those ControlMachine can be closed and expanded and I can't find a clean way to update the user controls position for any case.
annexe: Do you think it's good-practice using UserControl in this case or may be I should use a Template or StackPanel?
How would you implement this?

Best practice for this solution is using a ViewModel instead of UserControl and bind it in your grid.
you can use DependencyProperty and INotifyPropertyChanged for two way binding in your ViewModel.
So you'll not warn about disposing ... .

Related

Prevent UserControl from rendering each time it is used

I am developing plain WPF application on top of .NET 5.0.
I have navigation mechanism implemented based on DataTemplate(s)
<DataTemplate DataType="{x:Type viewmodels:FirstViewModel}">
<views:FirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:SecondViewModel}">
<views:SecondView />
</DataTemplate>
and CurrentViewModel
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
You can imagine there are navbar buttons changing the CurrentViewModel property.
Besides that I use Dependency Injection which provides all required services (including viewmodels).
Both FirstViewModel and SecondViewModel are singletons.
FirstView and SecondView are UserControl(s).
The problem:
Whenever I navigate between UserControls they re-render. This is very inconvinient, from performance perspective and application usability.
Let's say both UserControls have grids with searching features and more. I would like user to be able to navigate back to the same state which he left on the control (including focused elements and so on). Having singleton viewmodel allows to preserve state/values of the properties but it doesn't solve the problem of control being re-rendered whenever I navigate. I clearly see that each time FirstView constructor is being called on which it InitializeComponent() and later calls OnRender().
Grids have asynchronous Sources.
<DataGrid Style="{DynamicResource DataGridStyle}" ItemsSource="{Binding ActiveSearchResults, IsAsync=True}" AutoGenerateColumns="False" CanUserSortColumns="True" CanUserAddRows="False">
.
.
.
</DataGrid>
I don't want grid to be re-rendered each time I navigate and elements to lose their focus. When I navigate I also see the grid flikering.
Can you please suggest option I have?
Is it possible to have singleton UserControl?
Is it possible to have static property with a cached state?

How do I create a group of different UI elements in XAML based on datatypes of the bound values?

I have a list of parameters in an Observable Collection in my ViewModel that I want to be displayed as textboxes or comboboxes dependent on the datatype, but I'm struggling to find a way to implement any kind of conditional functionality in XAML.
So, say I have an observable collection of parameters (title, datatype, value):
Valid,Boolean,
Name,String,
Age,UInt,
I currently have these generating a sequence of textboxes in the window next to labels indicating the type and parameter name using:
<ItemsControl ItemsSource="{Binding Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Textbox Text="{Binding Value}"
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But I don't want this to be a textbox every time; I want to be able to have something like an "if" or a "switch" to make a combobox instead if the datatype facilitates pre-defined responses like True/False.
I've seen similar posts where the answers seemed to suggest using Triggers or DataTriggers to do this, which looks exactly like what I need. However, they always seem to put it in a Style, with Style.Triggers, rather than any of the elements I currently have. Any attempts to replicate what I've seen have failed due to slight differences in what we've used; The example will use something like ContentControl and bind the style with "Content = {…}" which I can't do because "Content" isn't something I can apply to "ItemControl", but I need ItemControl due to the way it implements ObservableCollection in a way Content control doesn't.
Is there a simple way to put "If datatype == Boolean, make a combobox here" sort of thing? If I can make this happen in the ViewModel, I'm open to that too. Don't want to be doing anything in the code-behind, as MVVM doesn't seem to play nice with it and has always bit me in the behind later down the road when I try to mix them and I can't get my values to interact properly.
Would very much appreciate any help.
I realise I could just have the user TYPE true/false and interpret it in the viewmodel as Boolean, but there's several reasons this won't really work in my case. For one thing, there will be many, many options.
Really think you should be using DataTemplates:
... xmlns:sys="clr-namespace:System;assembly=mscorlib"
<ItemsControl ItemsSource="{Binding Parameters}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type sys:Boolean}">
<Checkbox IsChecked="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<ContentPresenter Content="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can use your own types as well that it will auto detect and use the datatype for.

is there a way to close Usercontrol from ViewModel in WPF

I am working on a WPF application which is developed using MVVM pattern. The MainWindow has several Usercontrols that open when an action is performed. However, I want to close the Usercontrol once the actions are complete and on Clicking a button. I have looked in several places, but haven't had any luck with it so far. Any help would be appreciated.
It is being pointed out that my question is a duplicate of this :
Close View from ViewModel
But it is actually not, since that thread talks about closing a Window, mine is about closing an UserControl.
Adding some code to make it clear:
This is the ItemsControl in the first UserControl which hosts the second Usercontrol:
<Grid x:Name="UserControlGrid" Width="Auto" Height="auto" Margin="0,0,0,0">
<ItemsControl ItemsSource="{Binding ViewsToShow}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True" Width="auto" Height="auto"></Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
Now to open the second UserControl , in the first UserControl ViewModel, I do this:
private ObservableCollection<ObservableObject> viewsToShow = new ObservableCollection<ObservableObject>();
public ObservableCollection<ObservableObject> ViewsToShow
{
get
{
return viewsToShow;
}
set
{
viewsToShow = value;
OnPropertyChanged("ViewsToShow");
}
}
ViewsToShow.Add(new SecondUserControlViewModel());
Thank you
The answer is: you should not close your usercontrols (unless they're used as separate dialogs, and this is not your case, according to your comment above).
All changes in usercontrols visibility are about navigation. Once you logically navigate to a functionality involving another usercontrol, you have to hide old one and show new control. Usually this is done via template selection:
Two templates, one per UserControl, each associated with respective ViewModel:
<DataTemplate DataType="{x:Type ViewModels:FirstViewModel}">
<controls:FirstControl />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:SecondViewModel}">
<controls:SecondControl />
</DataTemplate>
Then later we declare a placeholder:
<ContentControl Content="{Binding ViewModelSelector}" />
Once ViewModelSelector property returns FirstViewModel, our placeholder will show FirstControl. If we navigate ViewModelSelector property to SecondViewModel, our placeholder would auto-replace FirstControl with SecondControl and vice versa.

ContentPresenter in ItemControl.ItemTemplate to show displaymemberpath of itemscontrol

I want to know is there anyway to put contentpresenter in itemtemplate of an itemscontrol to display my data. I don't want hard code binding like Text="{Binding username}" cause I am building a custom control, I think ContentPresenter is what I want. But after I tried using contentpresenter, it give me stackoverflowexception.
<ItemsControl ItemsSource="{Binding SelectedItems, ElementName=listbox}" DisplayMemberPath={Binding DisplayMemberPath}">
<ItemsControl.ItemPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Separator" Text=", "/>
<ContentPresenter/>
<!--<TextBlock Text="{Binding username}"/>-->
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
That's my code.
If without those seperator and itemtemplate, I able to display my data by just using the displaymemberpath, but it stack all the name together. I still finding any solution to solve it. I hope you can provide some ideas to do this.
The answer is no, you can't. A ContentPresenter is supposed to be used in a ControlTemplate, not a DataTemplate, so it is not the right control to use. From the linked page on MSDN:
You typically use the ContentPresenter in the ControlTemplate of a ContentControl to specify where the content is to be added.
What you can do alternatively, is to declare a number of DataTemplates in a Resources section (complete with Binding Paths) for different types of data and omit the x:Key directives, eg. do not name them. Also, do not specify one for the ItemsControl.ItemTemplate.
When doing this, WPF will implicitly select the correct DataTemplate for the relevant data type and so you can have different outputs for different data types. See the The DataType Property section of the Data Templating Overview page on MSDN for further explanation of this technique.
Yes, and it works well. Outside of a ContentControl's template, you must bind the Content by hand:
<ContentPresenter Content="{Binding username}"/>
I do this a great deal and it never misbehaves. ContentPresenter seems to be implemented for general use. I wonder if the API docs overstate its relationship to ContentControl.
I found an easier way to solve this problem by using horizontal listbox. Thanks for responses

Best way to bind a set of same-type ViewModels to a TabControl in MVVM / WPF

I have an existing ViewModel and View in an MVVM project. Effectively this View presents a collection of items in a particular, styled way. I'll call this existing ViewModel "CollectionPresenter".
Up to now, this has been presented as as follows in XAML:
<Grid>
<ns:CollectionPresenter />
</Grid>
Now, I want to have a dynamic collection of these "CollectionPresenter" view models made available ideally in a tab view.
My approach has been to define an observable collection of these "CollectionPresenters", creating them first on construction of the parent view model. The XAML above then changed to look something like this:
<TabControl ItemsSource="{TemplateBinding CollectionPresenters}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CollectionPresenterTitle}">
</DataTemplate>
<TabControl.ItemTemplate>
<TabControl.ContentTemplate>
... this is where things get confusing
</TabControl.ContentTemplate>
<TabControl>
You can see above my problem is the ContentTemplate.
When I load this up, I get a tab control and it has as many tabs as my observable collection of "CollectionPresenter" objects.
However, the content of the tab control is always empty.
Is this approach correct - and is there a better way regardless?
EDIT: ADDING SOME EXTRA THINGS TO MAKE IT CLEARER
I've tried the below, but it doesn't work. The XAML with the Tab Control (the binding to "Things" works fine):
<TabControl ItemsSource="{TemplateBinding Things}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="200" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="500" Height="500" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The definition for the "Things" observable collection (which is inside the templated parent (ParentObject) of the XAML with the tab control):
public static readonly DependencyProperty ThingsProperty =
DependencyProperty.Register("Things", typeof(ObservableCollection<Thing>), typeof(ParentObject), new PropertyMetadata(null));
public ObservableCollection<Thing> Things
{
get { return (ObservableCollection<Thing>)GetValue(ThingsProperty); }
set { SetValue(ThingsProperty, value); }
}
Stripped down version of the "Thing" view model:
public class Thing : ViewModelBase
{
public Thing()
{
}
public void Initialise(ObservableCollection<Thing> things, string thingName)
{
Things = things;
ThingName = thingName;
}
public static readonly DependencyProperty ThingNameProperty =
DependencyProperty.Register("ThingName", typeof(string), typeof(Thing), new PropertyMetadata(null));
public string ThingName
{
get { return (string)GetValue(ThingNameProperty); }
set { SetValue(ThingNameProperty, value); }
}
}
Looking at my answer to the WPF MVVM navigate views question, you can see this:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use an instance from one of these types in our application, these DataTemplates will tell the framework to display the related view instead.
Therefore, your solution is to simply not hard-code one single DataTemplate to the TabControl.ItemTemplate property, but to leave that blank instead. If you use multiple DataTemplates without providing x:Key values, then they will implicitly be applied when each data object is to be rendered in the TabControl.
UPDATE >>>
Using these DataTemplates should leave your TabControl looking like this:
<TabControl ItemsSource="{TemplateBinding Things}" />
I'm not sure why you're using a TemplateBinding there though as you don't need to define any new templates to get this working... therefore, you should be using a plain old Binding instead.
One other thing that you need to do is to use different data types for each item in the collection that you want to display differently. You could derive custom classes from your Thing class and so the collection could still be of type ObservableCollection<Thing>.

Categories

Resources