In WPF, adding UserControl C# object from ViewModel to View - c#

I have a UserControl in ViewModel which is a Property in ViewModel. I will obtain this UserControl from some dll's.
private GraphicControl _graphicCtrl;
public GraphicControl GraphicCtrl
{
get { return _graphicCtrl; }
set
{
_graphicCtrl = value;
NotifyPropertyChanged();
}
}
In the above property, GraphicCtrl is nothing but a UserControl. I want to display this in my View (xaml). So how should I achieve this.?

Here is a very simple example how to separate this
public class MainViewModel : ReactiveObject
{
public MainViewModel()
{
Stuff = new ObservableCollection<object>
{
new Person { FirstName = "Jon", LastName="Doe", },
new Car { Brand = "Ford", Model = "Model T" },
};
}
public IEnumerable Stuff { get; }
[Reactive] public object SelectedStuff { get; set; }
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Car
{
public string Brand { get; set; }
public string Model { get; set; }
}
As you can see, no dependency to any controls or other UI related stuff.
Now the view where I decide how to present the data from the ViewModel
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox
ItemsSource="{Binding Stuff}"
SelectedItem="{Binding SelectedStuff}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<TextBlock>
<Run Text="{Binding Brand}" /><Run Text=" - " /><Run Text="{Binding Model}" />
</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Person}">
<TextBlock>
<Run Text="{Binding FirstName}" /> <Run Text="{Binding LastName}" />
</TextBlock>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl
Grid.Column="1"
Content="{Binding SelectedStuff}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<local:CarControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Person}">
<local:PersonControl/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
the CarControl
<StackPanel>
<Label Content="Car" />
<Label Content="Brand" />
<TextBlock Text="{Binding Brand}" />
<Label Content="Model" />
<TextBlock Text="{Binding Model}" />
</StackPanel>
the PersonControl
<StackPanel>
<Label Content="Person" />
<Label Content="FirstName" />
<TextBlock Text="{Binding FirstName}" />
<Label Content="LastName" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
and finally a screenshot

I want to display this in my View (xaml). So how should I achieve this.?
Short answer: Use a ContentControl and bind its Content property to the UserControl:
<ContentControl Content="{Binding GraphicCtrl}" />
As others have already mentioned, exposing a UserControl from a view model is not MVVM though.
You should rather expose a POCO data object called Graphic or something and then use a DataTemplate to map this type to a UserControl in the view:
<ContentControl Content="{Binding Graphic}">
<ContentControl.ContentTemplate>
<DataTemplate>
<local:GraphicControl />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>

Related

I can't bind with the IsExpanded property on my treeview

I'm trying to bind with an IsExpanded property, but without success so far. I need to know when one of the groups is expanded.
<TreeView Grid.Row="1">
<!-- Encontrados -->
<TreeViewItem
FontWeight="Bold"
IsExpanded="True"
ItemsSource="{Binding Banco.Grupos}">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
Width="20"
Foreground="Green"
Kind="Done" />
<TextBlock Text="ENCONTRADOS: " />
<TextBlock Text="{Binding SaldoEncontrados, StringFormat='{}{0:C}', ConverterCulture='PT-BR'}" />
</StackPanel>
</TreeViewItem.Header>
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type model:Grupo}" ItemsSource="{Binding Transações}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Nome, StringFormat='{}{0}: '}" />
<TextBlock Text="{Binding ValorTotal, StringFormat='{}{0:C}', ConverterCulture='PT-BR'}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<CheckBox
Grid.Column="0"
FontWeight="Normal"
IsChecked="{Binding isEnabled}" />
<TextBlock
Grid.Column="1"
FontWeight="Normal"
Text="{Binding Transação.DataDaTransação, StringFormat='dd/MM/yyyy'}" />
<TextBlock
Grid.Column="2"
Margin="10,0,10,0"
FontWeight="Normal"
Text="{Binding Transação.Historico}" />
<TextBlock
Grid.Column="3"
HorizontalAlignment="Right"
FontWeight="Normal"
Text="{Binding Transação.Valor, StringFormat='{}{0:C}', ConverterCulture='PT-BR'}" />
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
<!-- Não Encontrados -->
<TreeViewItem
FontWeight="DemiBold"
IsExpanded="False"
ItemsSource="{Binding TransaçõesNãoEncontradas}">
<TreeViewItem.Header>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<materialDesign:PackIcon
Width="20"
Foreground="Red"
Kind="Error" />
<TextBlock Text="NÃO ENCONTRADOS: " />
<TextBlock Text="{Binding SaldoNaoEncontrados, StringFormat='{}{0:C}', ConverterCulture='PT-BR'}" />
</StackPanel>
</TreeViewItem.Header>
<TreeViewItem.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition x:Name="Coluna1" Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
FontWeight="Normal"
Text="{Binding DataDaTransação, StringFormat='dd/MM/yyyy'}" />
<TextBlock
Grid.Column="1"
Margin="10,0,10,0"
FontWeight="Normal"
Text="{Binding Historico}" />
<TextBlock
Grid.Column="2"
HorizontalAlignment="Right"
FontWeight="Normal"
Text="{Binding Valor, StringFormat='{}{0:C}', ConverterCulture='PT-BR'}" />
</Grid>
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeView>
My group class:
public class Grupo
{
public int Id { get; set; }
public int BancoId { get; set; }
public string Nome { get; set; }
public List<Transações> Transações { get; set; }
public decimal ValorTotal { get; set; }
public bool isExpanded { get; set; } = false;
public bool isZeroEnabled { get; set; }valores
}
public record Transações(int pk, int fk, bool isEnabled, OfxTransação Transação)
{
public bool isEnabled { get; set; } = isEnabled;
}
XAML:
Could someone help me bind the treeview's IsExpanded property so that I can know when a "Group" is expanded.
I created an example of my code: https://github.com/Foiolag/TreeviewExample
You can bind the IsExpanded property via ItemContainerStyle. Make sure to remove the explicite IsExpanded="True"
<!-- Encontrados -->
<TreeViewItem FontWeight="Bold"
ItemsSource="{Binding Banco.Grupos}">
<TreeViewItem.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeViewItem.ItemContainerStyle>
Hint 1: This style will be inherited over all children, the application will also try to bind to the non existing IsExpanded property on your Transações object. This won't throw any exceptions, just binding errors. For the sake of consistency, I would rather implement the IsExpanded property on all children.
Hint 2: Your model classes are not bindable (do not notify property changes). If you change the properties after the view initialization, the view won't react to your changes. You have to implement INotifyPropertyChanged or use a base class like BindableBase. Read this. If the properties are intended for read-only use, better to make them read-only (remove setters).
Hint 3: Please avoid using special characters for your variables and class names 😉
The DataContext of the item container (in this case the TreeViewItem) is always the data item itself.
With this in mind you can setup the binding using a Style:
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>

use binds in viewmodels

I am doing a project and in one xaml page I have some Textblock with bind like this
<PivotItem Header="Lista">
<ListView x:Name="List1" ItemsSource="{x:Bind ProdutoViewModel.Produtos}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="list:Produto">`
...
<StackPanel>
<TextBlock x:Name="nome" Text="{x:Bind Nome, Mode=OneWay}" />
<TextBlock Text="{x:Bind Preco, Mode=OneWay}" />
<TextBlock Text="{x:Bind Disponivel, Mode=OneWay}" />
<TextBlock Text="{x:Bind Fornecedor, Mode=OneWay}" />
<TextBlock Text="{x:Bind Categoria, Mode=OneWay}" />
<Image Source="Assets/mouse.png" />
<Image Source="Assets/teclado.png"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</PivotItem>
And then I have another pivot like this
<PivotItem>
<PivotItem Header="Carrinho">
<ListView x:Name="Cart" ItemsSource="{x:Bind EncomendaProdutoViewModel.EncomendaProdutos}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="list:EncomendaProduto">
<StackPanel>
<Button x:Name="pay" Content="Pagar" Click="Payment_click"/>
<TextBlock Text="{x:Bind Quantidade, Mode=OneWay}" />
<TextBlock Text="{x:Bind Preco, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</PivotItem>
How can I show the data from the first pivot into the second? I have a button on the first pivotitem for each product and when I press it, I want to add the data of that product to the second pivotitem.
Create an outer view-model, like this:
<Page.DataContext>
<vm:MasterViewModel x:Name="ViewModel" />
</Page.DataContext>
class MasterViewModel
{
public ProdutoViewModel ProdutoViewModel { get; set; }
public EncomendaProdutoViewModel EncomendaProdutoViewModel { get; set; }
}
Then bind to the Pivot like this:
<Pivot>
<PivotItem>
<ListView ItemsSource="{x:Bind ViewModel.ProdutoViewModel.Produtos}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}" />
</PivotItem>
<PivotItem>
<ListView ItemsSource="{x:Bind ViewModel.EncomendaProdutoViewModel.EncomendaProdutos}"
SelectedItem="{x:Bind ViewModel.EncomendaProdutoViewModelSelectedItem, Mode=TwoWay}" />
</PivotItem>
</Pivot>
Then, handle the selection in code-behind:
class MasterViewModel
{
public ProdutoViewModel ProdutoViewModel { get; set; }
public EncomendaProdutoViewModel EncomendaProdutoViewModel { get; set; }
Producto _ProdutoViewModel;
public Producto ProdutoViewModel
{
get { return _ProdutoViewModel; }
set {
_ProdutoViewModel = value;
EncomendaProdutoViewModel.EncomendaProdutos.Add(value);
}
}
}
I hope this makes sense.
Best of luck!

Add items to custom ListBox

I've this template of ListBoxItem that contains an Image and TextBlock. How to add an Item to this ListBox from code?
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding}" Width="16" />
<TextBlock Text="{Binding}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am assuming you will want to have a class which has imagesource and text properties in the lines of
public class TestClass()
{
public string ImageSrc {get; set;}
public string DisplayText {get; set;}
}
Add the objects to your collection
listBox.Items.Add(new TestClass() { ImageSrc = "blahblah", DisplayTest = "Test Display Text" });
and so on
Then you can use xaml in the lines of
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding ImageSrc}" Width="16" />
<TextBlock Text="{Binding DisplayText}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF TreeView more than one list in HierarchicalDataTemplate

My model is like so:
public class CPFF
{
public CMC Cmc { get; set; }
}
public class CMC
{
public List<CMCRecord> recordList { get; set; }
}
public class CMCRecord
{
public string name { get; set; }
public List<param> recordHeader { get; set; }
public List<param> paramList { get; set; }
public List<CmcStruct> structList { get; set; }
public bool IsEmptyRecord { get; set; }
public CpffConsts.CpffProjectVersion cpffProjectVersion { get; set; }
}
Here is my Xaml:
<TreeView DataContext="{Binding Cpff1StProjectObject}" SelectedItemChanged="TreeView_OnSelectedItemChanged" HorizontalContentAlignment="Stretch" >
<TreeViewItem Header="CMC" >
<TreeViewItem Header="RecordList" ItemsSource="{Binding Cmc.recordList}" >
<HierarchicalDataTemplate DataType="{x:Type types:CMCRecord}" ItemsSource="{Binding paramList}">
<TextBlock Text="{Binding Path=name, StringFormat={}CMCRecord : {0}}" />
</HierarchicalDataTemplate>
</TreeViewItem>
</TreeViewItem>
</TreeView>
<HierarchicalDataTemplate DataType="{x:Type types:param}">
<StackPanel>
<TextBlock Text="{Binding Path=name, StringFormat={}Name : {0}}" />
<TextBlock Text="{Binding Path=type, StringFormat={}Type : {0}}" />
<TextBlock Text="{Binding Path=description, StringFormat={}Description : {0}}" />
<TextBlock Text="{Binding Path=defaultValue, StringFormat={}DefaultValue : {0}}" />
<TextBlock Text="{Binding Path=domain_name, StringFormat={}domain_name : {0}}" />
<TextBlock Text="{Binding Path=notes, StringFormat={}Notes : {0}}" />
<TextBlock Text="{Binding Path=offset, StringFormat={}Offset : {0}}" />
<TextBlock Text="{Binding Path=bytes, StringFormat={}Bytes : {0}}" />
<TextBlock Text="{Binding Path=size, StringFormat={}Size : {0}}" />
<TextBlock Text="{Binding Path=min, StringFormat={}Min : {0}}" />
<TextBlock Text="{Binding Path=max, StringFormat={}Max : {0}}" />
</StackPanel>
</<HierarchicalDataTemplate DataType="{x:Type types:param}">
<StackPanel>
<TextBlock Text="{Binding Path=name, StringFormat={}Name : {0}}" />
<TextBlock Text="{Binding Path=type, StringFormat={}Type : {0}}" />
<TextBlock Text="{Binding Path=description, StringFormat={}Description : {0}}" />
<TextBlock Text="{Binding Path=defaultValue, StringFormat={}DefaultValue : {0}}" />
<TextBlock Text="{Binding Path=domain_name, StringFormat={}domain_name : {0}}" />
<TextBlock Text="{Binding Path=notes, StringFormat={}Notes : {0}}" />
<TextBlock Text="{Binding Path=offset, StringFormat={}Offset : {0}}" />
<TextBlock Text="{Binding Path=bytes, StringFormat={}Bytes : {0}}" />
<TextBlock Text="{Binding Path=size, StringFormat={}Size : {0}}" />
<TextBlock Text="{Binding Path=min, StringFormat={}Min : {0}}" />
<TextBlock Text="{Binding Path=max, StringFormat={}Max : {0}}" />
</StackPanel>
</HierarchicalDataTemplate>>
This is how my tree look like:
Since CMCRecord Contains more than one list I would like to reflect it into the tool.
However since ItemSource in the HierarchicalDataTemplate is bound to paramlist,
How can I show more than one list inside my TreeView?
You will need to create a union of the various lists as a list of type object (i.e. List<object>) and set that as the Children target in your HierarchicalDataTemplate and use WPF type inference to determine the HierarchicalDataTemplate to be used, much like you have done for type param

How can the property of object in an inner ItemsControl bind to the property of an object in an outer ItemsControl?

The title might sound convoluted but bear with me.
I have Rooms which contain Occupants:
public class Room
{
public string Name { get; set; }
public List<Person> Occupants { get; set; }
public bool AreOccupantsEditable { get; set; }
}
public class Person
{
public string Name { get; set; }
}
Here's an array of Rooms:
<ResourceDictionary>
<x:Array x:Key="Rooms" Type="local:Room">
<local:Room Name="Happy Room" AreOccupantsEditable="True">
<local:Room.Occupants>
<local:Person Name="Mindy" />
</local:Room.Occupants>
</local:Room>
<local:Room Name="Sad Room" AreOccupantsEditable="True">
<local:Room.Occupants>
<local:Person Name="Bob" />
<local:Person Name="Jane" />
</local:Room.Occupants>
</local:Room>
<local:Room Name="Kill Room" AreOccupantsEditable="False">
<local:Room.Occupants>
<local:Person Name="Mork" />
<local:Person Name="Dave" />
<local:Person Name="Ryan" />
</local:Room.Occupants>
</local:Room>
</x:Array>
</ResourceDictionary>
Here is an ItemsControl in an ItemsControl, to display rooms and their occupants:
<ItemsControl ItemsSource="{Binding Source={StaticResource Rooms}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- room name -->
<TextBlock Text="{Binding Path=Name}" />
<ItemsControl ItemsSource="{Binding Path=Occupants}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- occupant name -->
<TextBox Text="{Binding Path=Name}" Margin="20,0,0,0" IsEnabled="{Binding ???}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Without Person having a reference to Room, how can I bind the IsEnabled property of the TextBox to the AreOccupantsEditable property of the Room a Person is in?
If it helps, here's an example project: http://dl.dropbox.com/u/4220513/ItemsControl-Binding.zip
You can use RelativeSource to access the outer DataContext :
IsEnabled="{Binding Path=DataContext.AreOccupantsEditable, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
you can also use IsEnabled property of itemControl of Occupants to disable whole itemControl
<ItemsControl ItemsSource="{Binding Path=Occupants}" IsEnabled="{Binding Path=AreOccupantsEditable}">

Categories