Zoom a datagrid centered - c#

I have to write an application which is able to zoom in and out two different layers at the same time.
The background layer contains an image which is always centered during zooming. The second layer is based, however, on the upper edge of the parent container.
Here is my XAML-Code:
<ScrollViewer Visibility="{Binding LeerformularIsVisible}" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Image x:Name="DZBackgroundImage" Source="{Binding DZGridBackground}" Stretch="None" />
<DataGrid ColumnWidth="7" MinColumnWidth="7" RowHeight="15" BorderBrush="{x:Null}" AutoGenerateColumns="True" CanUserAddRows="False"
Visibility="{Binding GridIsVisible}" ItemsSource="{Binding DPGridCR}" Margin="5,50,70,0" Background="Transparent" RowBackground="Transparent"
HeadersVisibility="None" SelectionUnit="Cell" CanUserResizeRows="False" HorizontalGridLinesBrush="{Binding LineBrush}" VerticalGridLinesBrush="{Binding LineBrush}">
<i:Interaction.Behaviors>
<ViewModel:IgnoreMouseWheelBehavior />
</i:Interaction.Behaviors>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#325EB226" />
<Setter Property="BorderBrush" Value="#325EB226" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding AddFieldDefinitionCommand}" Header="Feld hinterlegen" Icon="pack://application:,,,/Images/Designer/field.png" />
<MenuItem Command="{Binding AddFunctionCommand}" Header="Funktion hinterlegen" Icon="pack://application:,,,/Images/Designer/function.png" />
<MenuItem Command="{Binding RemoveFieldDefinitionCommand}" Header="Aktuelle Felddefinition entfernen" Icon="pack://application:,,,/Images/Designer/remove_field.png" />
<MenuItem Command="{Binding CutCommand}" Header="Ausschneiden" Icon="pack://application:,,,/Images/Zwischenablage/FI_Ausschneiden_16x16.png" />
<MenuItem Command="{Binding CopyCommand}" Header="Kopieren" Icon="pack://application:,,,/Images/Zwischenablage/FI_Kopieren_16x16.png" />
<MenuItem Command="{Binding PasteCommand}" Header="Einfügen" Icon="pack://application:,,,/Images/Zwischenablage/FI_Einfuegen_16x16.png" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding ZoomFactor}" ScaleY="{Binding ZoomFactor}" />
</Grid.LayoutTransform>
</Grid>
</ScrollViewer>
I get the values for the ZoomFactor binding from my view-model.
My goal is to zoom the two layers simultaneously and overlapped.
Thanks in advance.
Edit:
Here comes the whole XAML-File.
<UserControl x:Class="Controls.DZLeerformularGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ViewModel="clr-namespace:ViewModel;assembly=ViewModel"
xmlns:Behaviors="clr-namespace:ViewModel.Behaviors;assembly=ViewModel"
xmlns:Extension="clr-namespace:Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="DZLeerformularGridControl">
<ScrollViewer Behaviors:AdvancedZooming.KeepInCenter="true" Visibility="{Binding LeerformularIsVisible}" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Viewbox RenderTransformOrigin="0.5, 0.5" Stretch="Uniform">
<Grid>
<Image x:Name="DZBackgroundImage" Source="{Binding DZGridBackground}" Stretch="None" />
<DataGrid ColumnWidth="7" MinColumnWidth="7" RowHeight="15" BorderBrush="{x:Null}" AutoGenerateColumns="True" CanUserAddRows="False"
Visibility="{Binding GridIsVisible}" ItemsSource="{Binding DPGridCR}" Margin="5,50,70,0" Background="Transparent" RowBackground="Transparent"
HeadersVisibility="None" SelectionUnit="Cell" CanUserResizeRows="False" HorizontalGridLinesBrush="{Binding LineBrush}" VerticalGridLinesBrush="{Binding LineBrush}">
<i:Interaction.Behaviors>
<Behaviors:IgnoreMouseWheelBehavior />
</i:Interaction.Behaviors>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#325EB226" />
<Setter Property="BorderBrush" Value="#325EB226" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding AddFieldDefinitionCommand}" Header="Feld hinterlegen" Icon="pack://application:,,,/Images/Designer/field.png" />
<MenuItem Command="{Binding AddFunctionCommand}" Header="Funktion hinterlegen" Icon="pack://application:,,,/Images/Designer/FI_Taschenmesser_16x16.png" />
<MenuItem Command="{Binding RemoveFieldDefinitionCommand}" Header="Aktuelle Felddefinition entfernen" Icon="pack://application:,,,/Images/Designer/remove_field.png" />
<MenuItem Command="{Binding CutCommand}" Header="Ausschneiden" Icon="pack://application:,,,/Images/Zwischenablage/FI_Ausschneiden_16x16.png" />
<MenuItem Command="{Binding CopyCommand}" Header="Kopieren" Icon="pack://application:,,,/Images/Zwischenablage/FI_Kopieren_16x16.png" />
<MenuItem Command="{Binding PasteCommand}" Header="Einfügen" Icon="pack://application:,,,/Images/Zwischenablage/FI_Einfuegen_16x16.png" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
<Viewbox.RenderTransform>
<ScaleTransform ScaleX="{Binding ZoomFactor}" ScaleY="{Binding ZoomFactor}" />
</Viewbox.RenderTransform>
</Viewbox>
</ScrollViewer>
</UserControl>
Following comes the C# code for my ViewModel:
using System.Collections.Generic;
using System.Data;
using System.Windows.Media;
using System.Windows.Input;
using ViewModelBase;
using System.Windows;
namespace ViewModel
{
public class DZLeerformularGridViewModel : ViewModelBase.ViewModelBase
{
#region Fields
#region Command Fields
private RelayCommand addFieldDefinition;
private RelayCommand removeFieldDefinition;
private RelayCommand addFunction;
private RelayCommand cutCommand;
private RelayCommand copyCommand;
private RelayCommand pasteCommand;
#endregion
private string leerformularIsVisible;
private string dzGridBackground;
private string gridIsVisible;
private SolidColorBrush lineBrush;
private DataTable dpGridCR;
private double zoomFactor;
#endregion
public DZLeerformularGridViewModel()
{
LineBrush = Brushes.LightGray;
}
#region Properties
public string LeerformularIsVisible
{
get { return leerformularIsVisible; }
set
{
if (leerformularIsVisible != value)
{
leerformularIsVisible = value;
OnPropertyChanged("LeerformularIsVisible");
}
}
}
public string DZGridBackground
{
get { return dzGridBackground; }
set
{
if (dzGridBackground != value)
{
System.Drawing.Image img = System.Drawing.Image.FromFile(value);
if (img.Height > img.Width)
generateColsAndRows(94, 70); // DIN A4 Hochformat
else
generateColsAndRows(70, 94); // DIN A4 Querformat
dzGridBackground = value;
OnPropertyChanged("DZGridBackground");
}
}
}
public string GridIsVisible
{
get { return gridIsVisible; }
set
{
if (gridIsVisible != value)
{
gridIsVisible = value;
OnPropertyChanged("GridIsVisible");
}
}
}
public SolidColorBrush LineBrush
{
get { return lineBrush; }
set
{
if (lineBrush != value)
{
if (!value.Equals(Brushes.LightGray) || !value.Equals(Brushes.Gray) || ! value.Equals(Brushes.LightSlateGray) || !value.Equals(Brushes.SlateGray) ||
!value.Equals(Brushes.Black) || !value.Equals(Brushes.Red) || !value.Equals(Brushes.DarkGray))
lineBrush = Brushes.LightGray;
else
lineBrush = value;
OnPropertyChanged("LineBrush");
}
}
}
public DataTable DPGridCR
{
get { return dpGridCR; }
set
{
if (dpGridCR != value)
{
dpGridCR = value;
OnPropertyChanged("DPGridCR");
}
}
}
public double ZoomFactor
{
get { return zoomFactor; }
set
{
if (zoomFactor != value)
{
zoomFactor = value;
OnPropertyChanged("ZoomFactor");
}
}
}
#endregion
#region Command Properties
public ICommand AddFieldDefinitionCommand
{
get
{
if (addFieldDefinition == null)
addFieldDefinition = new RelayCommand(p => ExecuteAddFieldDefinitionCommand());
return addFieldDefinition;
}
}
public ICommand RemoveFieldDefinitionCommand
{
get
{
if (removeFieldDefinition == null)
removeFieldDefinition = new RelayCommand(p => ExecuteRemoveFieldDefinitionCommand());
return removeFieldDefinition;
}
}
public ICommand AddFunctionCommand
{
get
{
if (addFunction == null)
addFunction = new RelayCommand(p => ExecuteAddFunctionCommand());
return addFunction;
}
}
public ICommand CutCommand
{
get
{
if (cutCommand == null)
cutCommand = new RelayCommand(p => ExecuteCutCommand());
return cutCommand;
}
}
public ICommand CopyCommand
{
get
{
if (copyCommand == null)
copyCommand = new RelayCommand(p => ExecuteCopyCommand());
return copyCommand;
}
}
public ICommand PasteCommand
{
get
{
if (pasteCommand == null)
pasteCommand = new RelayCommand(p => ExecutePasteCommand());
return pasteCommand;
}
}
#endregion
#region Methods
private void generateColsAndRows(int amountOfCols, int amountOfRows)
{
DataTable table = new DataTable();
List<DataColumn> cols = new List<DataColumn>();
for (int i = 0; i < amountOfCols; i++)
{
DataColumn column = new DataColumn();
column.ReadOnly = true;
table.Columns.Add(column);
cols.Add(column);
}
for (int i = 0; i < amountOfRows; i++)
{
DataRow row = table.NewRow();
foreach (DataColumn col in cols)
row[col] = " ";
table.Rows.Add(row);
}
DPGridCR = table;
}
#endregion
#region Command Methods
private void ExecuteAddFieldDefinitionCommand()
{
MessageBox.Show("Add field definition");
}
private void ExecuteRemoveFieldDefinitionCommand()
{
MessageBox.Show("Remove field definition");
}
private void ExecuteAddFunctionCommand()
{
MessageBox.Show("Add function");
}
private void ExecuteCutCommand()
{
MessageBox.Show("Cut");
}
private void ExecuteCopyCommand()
{
MessageBox.Show("Copy");
}
private void ExecutePasteCommand()
{
MessageBox.Show("Paste");
}
#endregion
}
}
When I zoom in the image, no horizontal scrollbar appears. When I zoom out the image, the vertical scrollbar don't disappear. This effect is due to the use of RenderTransform. If I use LayoutTransform the DataGrid is bound to the upper edge of Grid or ViewBox. How could I solve this problem? In my opinion I require a combination of LayoutTransform and RenderTransfrom.

You can create an attached behavior and attach the same to the scroll viewer. so the behavior will detect the change in size of the content and will maintain the content in center of the scroll viewer
here is a link to a similar question I answered earlier, you can find that behavior class in the answer
Maintain scrollviewer's relative scrollbar offset when resizing child
so take the AdvancedZooming class from the answer in the link above and set the following property to the scroll viewer
<ScrollViewer Visibility="{Binding LeerformularIsVisible}"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
l:AdvancedZooming.KeepInCenter="True">
where l: refers to the namespace to your project. and that is all what you need to keep the content in center while zooming
EDIT
<ScrollViewer Visibility="{Binding LeerformularIsVisible}"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
l:AdvancedZooming.KeepInCenter="True">
<Viewbox>
<Grid>
<Image x:Name="DZBackgroundImage"
Source="{Binding DZGridBackground}"
Stretch="None" />
<DataGrid>
...
</DataGrid>
</Grid>
<Viewbox.LayoutTransform>
<ScaleTransform ScaleX="{Binding ZoomFactor}"
ScaleY="{Binding ZoomFactor}" />
</Viewbox.LayoutTransform>
</Viewbox>
</ScrollViewer>
if you find the size of datagrid or image is too small or too big then try adjusting the width/height of grid, image or datagrid, viewbox will scale accordingly

Related

How to dynamic enable/disable a ComboBox from ViewModel with a bool

I want my ComboBox to be disabled when my collection is null or empty and to be enabled when I update the collection and fill it, in the same way as my "Connect" button.
I tried IsEnabled="{Binding CanConnect }", but it starts disabled and is not enabled when I fill the collection with the button uppdate.
I typed this with MVVM:
Model.cs
internal class PuertosCollection : ObservableCollection<Puerto>
{
}
internal class Puerto
{
public string Nombre { get; set; }
public SerialPort Valor { get; set; }
public override string ToString()
{
return Nombre;
}
}
ViewModel.cs
public bool CanConnect
{
get
{
return CurrentPuerto?.Valor.IsOpen != null;
}
}
private ICommand conectarCommand;
public ICommand ConectarCommand
{
get
{
if (conectarCommand == null)
conectarCommand = new RelayCommand(new Action(Conectar), () => CanConnect);
return conectarCommand;
}
}
private void Conectar()
{
currentPuerto.Valor.Open();
}
private Puerto currentPuerto;
public Puerto CurrentPuerto
{
get { return currentPuerto; }
set
{
currentPuerto = value;
RaisePropertyChanged("CurrentPuerto");
}
}
private PuertosCollection listaPuertos;
public PuertosCollection ListaPuertos
{
get { return listaPuertos; }
set
{
listaPuertos = value;
if (value != null && value.Count > 0)
{
CurrentPuerto = value[0];
}
RaisePropertyChanged("ListaPuertos");
}
}
private ICommand listarPuertosCommand;
public ICommand ListarPuertosCommand
{
get
{
if (listarPuertosCommand == null)
listarPuertosCommand = new RelayCommand(new Action(ListarPuertos));
return listarPuertosCommand;
}
}
private void ListarPuertos()
{
ListaPuertos = Generator.Puertos();
}
View.xaml
<Window.Resources>
<vm:ConfiguracionViewModel x:Key="ConfiguracionVM"/>
<vm:DatoViewModel x:Key="DatoVM"/>
</Window.Resources>
<DockPanel>
<StackPanel DataContext="{StaticResource ConfiguracionVM}" DockPanel.Dock="Top" Orientation="Horizontal" HorizontalAlignment="Left" Margin="8">
<Button Content="Conectar" Command="{Binding ConectarCommand}" Margin="0,0,8,0"/>
<Label Content="Puerto:" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding ListaPuertos}" SelectedItem="{Binding CurrentPuerto}" IsEnabled="{Binding CanConnect }" Width="Auto" VerticalContentAlignment="Center" Margin="0,0,8,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ListarPuertosCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Button Command="{Binding ListarPuertosCommand}" VerticalAlignment="Center" Margin="0,0,8,0">
<StackPanel>
<Image Source="/Recursos/Imagenes/actualizar_96px.png" Width="32" Height="32" />
</StackPanel>
</Button>
<Button VerticalAlignment="Center">
<StackPanel>
<Image Source="/Recursos/Imagenes/ajustes_48px.png" Width="32" Height="32"/>
</StackPanel>
</Button>
</StackPanel>
<StatusBar DockPanel.Dock="Bottom">
<Label Content="Statusbar"/>
</StatusBar>
<DataGrid DataContext="{StaticResource DatoVM}">
</DataGrid>
</DockPanel>
I am learning MVVM with this tutorial
This is very simple.
First create a Converter that receives an integer that corresponds to the amount of items you have in the collection, if the amount is greater than zero it will return a True, if not it will return a False.
using System;
using System.Windows.Data;
namespace MyProject
{
public class CountToBoolean : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value is int) ? ((int)value) > 0 : false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Then you create an instance of the Converter in the App.xaml or in the window you want to use it.
<cv:CountToBoolean x:Key="CountToBoolean"/>
And finally you apply it to the ComboBox:
<ComboBox ItemsSource="{Binding ListaPuertos}" SelectedItem="{Binding CurrentPuerto}" IsEnabled="{Binding ListaPuertos.Count, Converter={StaticResource CountToBoolean}}" Width="Auto" VerticalContentAlignment="Center" Margin="0,0,8,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ListarPuertosCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
That should do it.
I tried IsEnabled="{Binding CanConnect}", but it starts disabled and is not enabled when I fill the collection with the button uppdate.
At least from your provided code, you do not trigger the property changed event for the CanConnect property when it is changed.
Disabling the combo box
You can add a style to the ComboBox that disables it if the ListaPuertos collection is null or empty.
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ListaPuertos}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding ListaPuertos.Count}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>

WPF Treeview - Define Parent and Children Nodes?

I'm trying to implement a TreeView in WPF to show children that display the charge activity, adjustment activity, etc on a daily account balance. I was going to start by first rendering the daily account balance details as the parent node (which I've done successfully), and then try, for instance, showing a text segment as the child for testing and then expanding from there to show children that I would preferably render as tables to show what was charged that day, and then separately what was adjusted on a charge that day, etc. This is where I'm stuck.
Here's my XAML code:
<Window x:Class="Client_Invoice_Auditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Client_Invoice_Auditor"
xmlns:self="clr-namespace:Client_Invoice_Auditor.CoreClientAR"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="80*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<StackPanel Orientation="Vertical">
<DockPanel VerticalAlignment="Top" Height="20" Panel.ZIndex="1">
<Menu Name="fileMenu" Width="Auto" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Open Account File" Click="menuOpenFile_Click"/>
<MenuItem Header="Exit" Click="menuExit_Click"/>
</MenuItem>
<MenuItem Header="Options">
<!--<MenuItem Header="Update" Click="update_Click"/>-->
<MenuItem Header="About" Click="about_Click"/>
</MenuItem>
</Menu>
</DockPanel>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Center" Height="Auto">
<StackPanel Width="Auto" Orientation="Horizontal" HorizontalAlignment="Center">
<Border BorderBrush="MediumAquamarine" BorderThickness="2">
<Label Name="AccountNumber"/>
</Border>
<Border BorderBrush="MediumAquamarine" BorderThickness="2">
<Label Name="AcctDesc"/>
</Border>
<Border BorderBrush="MediumAquamarine" BorderThickness="2">
<Label Name="Organization"/>
</Border>
</StackPanel>
</WrapPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Label Margin="20,10,0,0" Content="Activity Date Time" />
<Label Margin="60,10,0,0" Content="Beginning Balance" />
<Label Margin="10,10,0,0" Content="Charge Amount" />
<Label Margin="30,10,0,0" Content="Adjustments" />
<Label Margin="40,10,0,0" Content="Payments" />
<Label Margin="60,10,0,0" Content="End Balance" />
</StackPanel>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Grid.Column="0">
<TreeView Name="DABView">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:dailyAccountBalance}">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Width="150" Text="{Binding DabActivityDate}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding BegBalance}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding ChrgAmount}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding AdjAmount}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding PmtAmount}" />
<TextBlock Width="100" Margin="20,0,0,0" Text="{Binding EndBalance}" />
</StackPanel>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="TestChild">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="YOU IZ GOOD" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Grid>
</Window>
My Main Window's codebehind:
using Client_Invoice_Auditor.CoreClientAR;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Client_Invoice_Auditor
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string accountFile;
public MainWindow()
{
InitializeComponent();
OpenAccountFile();
}
private void OpenAccountFile()
{
accountFile = "";
//errorFilters.Clear();
// Displays an OpenFileDialog so the user can select a file.
Microsoft.Win32.OpenFileDialog openFileDialog1 = new Microsoft.Win32.OpenFileDialog();
openFileDialog1.Filter = "Account Files|*.acct";
openFileDialog1.Title = "Select an account file";
if (openFileDialog1.ShowDialog() == true)
{
// Assign the cursor in the Stream to the Form's Cursor property.
accountFile = openFileDialog1.FileName;
//openFileDialog1.Dispose();
}
if (accountFile == "")
{
/*System.Windows.MessageBox.Show("File must be selected in order to continue - exiting now."
, "No File Selected", MessageBoxButton.OK);
this.Close();*/
if (!AcctDesc.HasContent)
{
AcctDesc.Content = "No account file Loaded";
//Version version = Assembly.GetExecutingAssembly().GetName().Version;
}
}
else
{
//openFileDialog1 = null;
Console.WriteLine("Account file path is: " + accountFile);
DataTable dataAR = new DataTable();
try
{
Tuple<accountARHeader, List<dailyAccountBalance>, DataTable> loadedAR = dabARLoader.LoadARData(accountFile);
//dataAR = loadedAR.Item2;
AccountNumber.Content = "Account Number: " + loadedAR.Item1.AccountNumber;
AcctDesc.Content = "Description: " + loadedAR.Item1.AccountDescription;
Organization.Content = "Client Organization: " + loadedAR.Item1.OrganizationName;
//TreeViewItem dummy = new TreeViewItem();
//dummy.DataContext = "Hi";
//loadedAR.Item2.First().Dummy.Add("La");
//DABView.Items.Add(dummy);
DABView.ItemsSource = loadedAR.Item2;
//DABView.DisplayMemberPath = "A";
}
catch (Exception e)
{
System.Windows.MessageBox.Show("I don't wanna open this file! Try another. Error: " + e.Message);
OpenAccountFile();
}
}
}
private void menuOpenFile_Click(object sender, RoutedEventArgs e)
{
OpenAccountFile();
}
private void menuExit_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void about_Click(object sender, RoutedEventArgs e)
{
System.Windows.MessageBox.Show("I heard you like clicking buttons.");
}
}
}
And finally, my relevant class file:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace Client_Invoice_Auditor.CoreClientAR
{
public class dailyAccountBalance
{
double dabID;
DateTime dabActivityDate;
double begBalance;
double endBalance;
double chrgAmount;
double adjAmount;
double pmtAmount;
string dabActivitySegment;
public double DabID { get => dabID; set => dabID = value; }
public double BegBalance { get => begBalance; set => begBalance = value; }
public double EndBalance { get => endBalance; set => endBalance = value; }
public string DabActivitySegment { get => dabActivitySegment; set => dabActivitySegment = value; }
public DateTime DabActivityDate { get => dabActivityDate; set => dabActivityDate = value; }
public double ChrgAmount { get => chrgAmount; set => chrgAmount = value; }
public double AdjAmount { get => adjAmount; set => adjAmount = value; }
public double PmtAmount { get => pmtAmount; set => pmtAmount = value; }
public ObservableCollection<dailyAccountBalance> Items { get; set; }
public List<string> Dummy { get => dummy; set => dummy = value; }
public IList<TreeViewItem> DummyItems { get => dummyItems; set => dummyItems = value; }
public dailyAccountBalance()
{
this.Items = new ObservableCollection<dailyAccountBalance>();
}
List<string> dummy = new List<string>();
IList<TreeViewItem> dummyItems;
}
}
Is there a way, for instance, that I can show a dummy string as ONE child in the TreeView when it expands? Do I need to implement expand methods for this?
Thanks so much in advance.
Typically what I've done in the past is add a DummyNode to the TreeNode, bind the "IsExpanded" TreeViewItem property in the TreeViewItem style to a IsExpanded property on my Node class in my View Model, and add a bool _isLoaded flag. Then when the IsExpanded it triggered I check if _isLoaded is true, and if not I then go and load all of the subsequent data and set the _isLoaded to true.
btw... I use Prism to give me my BindableBase class that implements the INotifyPropertyChanged event for OnPropertyChanged. You can choose to implement that yourself instead of using BindableBase.
Example:
Node Class
public class TreeData : BindableBase
{
private bool _isExpanded = false;
private bool _isSelected = false;
private bool _hasChildren = false;
private Func<List<TreeData>> _getChildrenMethod;
private bool _isLoaded = false;
public TreeData(string value, bool hasChildren, Func<List<TreeData>> getChildren)
{
Value = value;
_hasChildren = hasChildren;
_getChildrenMethod = getChildren;
if(hasChildren)
{
Children.Add(new TreeNode("Dummy", false, null));
}
}
public ObservableCollection<TreeData> Children { get; set; } = new ObservableCollection<TreeData>();
public string Value {get;set;}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged(); }
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
_isExpanded = value;
if(!_isLoaded)
{
// Call load method here
if(_addChildrenMethod != null && _hasChildren)
{
Children.Clear();
Children.AddMany(_addChildrenMethod());
}
_isLoaded = true;
}
OnPropertyChanged();
}
}
}
View Model Class (owns the list of items)
public class BusinessViewModel
{
public ObservableCollection<TreeData> Items { get; set; } = new ObservableCollection<TreeData>();
public BusinessViewModel()
{
Items.Add(new TreeData("Root Item", hasChildren: true, GetChildren));
}
public List<TreeData> GetChildren()
{
return new List<TreeData>()
{
new TreeData("Child", hasChildren: false, null);
};
}
}
Then the XAML
<TreeView ItemSource="{Binding Items}" >
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Blue" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
</TreeView>
Hope that helps :D

Visually report progres

So basically I want a control in which I add dynamically new lines, where every lines represent an operation that i'm doing. To achieve that i created a textbox like this one:
<TextBox Grid.Row="1" Text="{Binding CurrRow}" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"/>
and I update the CurrRow property in my view model as following:
for (index = 0; index < 100; index++)
{
CurrRow = CurrRow + index.ToString();
//various operation
CurrRow = CurrRow + Environment.NewLine;
}
these is just an example to give the idea. The output is what I expected. However I would like something less "static" from visual perspective. For instance, i would like to add animated "..." within the line representing the operation that is currently in work, and i don't know if the TextBox is the right choose in this context. So my question is : How can i make a "report viewer" in WPF?
Here's a MVVM example using a few libraries I personally recommend :
View template (Xaml only):
<Window
x:Class="Sandbox.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Sandbox"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Test"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ProgressBar.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.CheckBox.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ListBox.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.RadioButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ToggleButton.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="0">
<ItemsControl
MaxWidth="300"
Margin="16,8"
ItemsSource="{Binding LongTasks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFinished}" Value="False">
<DataTrigger.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel Margin="12">
<ProgressBar
HorizontalAlignment="Center"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Style="{StaticResource MaterialDesignCircularProgressBar}"
Value="{Binding Progress}" />
<TextBlock
VerticalAlignment="Center"
Style="{StaticResource MaterialDesignDisplay1TextBlock}"
Text="Task running" />
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
Margin="12"
VerticalAlignment="Center"
Style="{StaticResource MaterialDesignDisplay1TextBlock}"
Text="Task finished" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Button
Grid.Row="1"
Margin="12"
HorizontalAlignment="Right"
Command="{Binding AddLongTask}"
Content="{materialDesign:PackIcon Kind=Plus,
Size=32}"
Style="{StaticResource MaterialDesignFloatingActionButton}" />
</Grid>
</Window>
```
Key point here you seem to want is to use a DataTrigger to change a ContentControl's ContentTemplate based on your condition so you can display something completely different.
The ViewModel to emulate the long run tasks in background :
using System;
using System.Reactive.Linq;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace Sandbox
{
public class SandboxNotifiableViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
{
var memberExpression = (MemberExpression) projection.Body;
this.RaisePropertyChanged(memberExpression.Member.Name);
}
public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class TestViewModel : SandboxNotifiableViewModel
{
private class SandBoxCommand : ICommand
{
private readonly Action cbk;
public event EventHandler CanExecuteChanged;
private void WarningRemover()
=> this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
public SandBoxCommand(Action cbk)
{
this.cbk = cbk;
}
public bool CanExecute(object parameter)
=> true;
public void Execute(object parameter)
=> this.cbk?.Invoke();
}
public TestViewModel()
{
this.AddLongTask = new SandBoxCommand(this.AddLongTaskAction);
this.LongTasks = new ObservableCollection<LongTaskViewModel>();
}
public ObservableCollection<LongTaskViewModel> LongTasks { get; }
private void AddLongTaskAction()
=> this.LongTasks.Add(new LongTaskViewModel());
public ICommand AddLongTask { get; }
}
public class LongTaskViewModel : SandboxNotifiableViewModel
{
private bool isFinished;
private int progress;
public LongTaskViewModel()
{
this.Progress = 0;
this.IsFinished = false;
// Refresh progress every 10ms 100 times
Observable.Interval(TimeSpan.FromMilliseconds(10))
.Select(x => x + 1) // 1 to 100
.Take(100)
// Here we make sure observable callback is called on dispatcher thread
.ObserveOnDispatcher()
.SubscribeOnDispatcher()
.Subscribe(this.OnProgressReported, this.OnLongTaskFinished);
}
public bool IsFinished
{
get => this.isFinished;
set
{
this.isFinished = value;
this.RaisePropertyChanged();
}
}
public int Progress
{
get => this.progress;
set
{
this.progress = value;
this.RaisePropertyChanged();
}
}
public void OnProgressReported(long dummyval)
{
this.Progress = (int) dummyval;
}
public void OnLongTaskFinished()
{
this.IsFinished = true;
}
}
}
I used Rx.NET to handle async notifications (here progress emulation) and MaterialDesignInXamlToolkit for the global styling

How to enable resizing of a custom shape in a listbox + canvas

Based on a project from the answer to this question:
Graph nodes coordinates evaluation
and using the MVVM Light Toolkit I'm trying to develop an application that would allow the user not only to move items all around the canvas, but also resize them. Here is what I have so far:
http://screenshooter.net/4766406/tfcqpjw
Main Window XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
xmlns:localvm="clr-namespace:WpfApplication2.ViewModel"
Title="MainWindow" Height="350" Width="927.985" x:Name="view">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="EditorStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<DockPanel>
<StackPanel Width="95" DockPanel.Dock="Left" Margin="5,0,0,0">
<local:ButtonsPanel/>
</StackPanel>
<Grid Margin="10">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<CompositeCollection x:Key="Col">
<CollectionContainer Collection="{Binding DataContext.Notes,Source={x:Reference view}}"/>
</CompositeCollection>
<DataTemplate DataType="{x:Type localvm:NoteViewModel}">
<Grid>
<Thumb DragDelta="Thumb_Drag"
IsEnabled="{Binding IsSelected,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Canvas>
<Path Fill="#FFAA0000" Data="{Binding ShapeGeometry}" Stretch="Fill" x:Name="Path"/>
</Canvas>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
<Setter TargetName="Path" Property="Fill" Value="Red"/>
</DataTrigger>
<Trigger Property="IsDragging" Value="True">
<Setter TargetName="Path" Property="Fill" Value="Green"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Grid>
</DataTemplate>
</Grid.Resources>
<ListBox SelectedItem="{Binding SelectedNote}"
PreviewMouseMove="ListBox_PreviewMouseMove"
PreviewMouseDown="ListBox_PreviewMouseDown">
<ListBox.Template ... > <!-- Collapsed -->
</ListBox.Template>
<ListBox.ItemsSource>
<StaticResource ResourceKey="Col"/>
</ListBox.ItemsSource>
<ListBox.ItemsPanel ...> <!-- Collapsed -->
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<!-- Moves the object -->
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter x:Name="Content"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</DockPanel>
Thumb's DragDelta from the MainWindow code behind + the constructor:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void Thumb_Drag(object sender, DragDeltaEventArgs e)
{
var thumb = sender as Thumb;
if (thumb == null)
return;
var note = thumb.DataContext as NoteViewModel;
if (note == null)
return;
note.X += e.HorizontalChange;
note.Y += e.VerticalChange;
}
Important properties and methods from the MainViewModel :
public class MainViewModel : ViewModelBase
{
private ObservableCollection<NoteViewModel> _notes;
public ObservableCollection<NoteViewModel> Notes
{
get { return _notes ?? (_notes = new ObservableCollection<NoteViewModel>()); }
}
private NoteViewModel _selectedNote;
public NoteViewModel SelectedNote {...}
public MainViewModel()
{
_notes = new ObservableCollection<NoteViewModel>(NotesDataSource.GetRandomNotes());
}
#region Creating New Notes
private bool _creatingNewNote;
public bool CreatingNewNote
{
get { return _creatingNewNote; }
set
{
_creatingNewNote = value;
RaisePropertyChanged("CreatingNewNote");
if (value)
CreateNewNote();
else
RemoveNewObjects();
}
}
public void CreateNewNote()
{
var newnote = new NoteViewModel()
{
Name = "Note" + (Notes.Count + 1),
IsNew = true
};
Notes.Add(newnote);
SelectedNote = newnote;
}
public void RemoveNewObjects()
{
Notes.Where(x => x.IsNew).ToList().ForEach(x => Notes.Remove(x));
}
#endregion
}
NoteViewModel:
public class NoteViewModel : ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
#region Coordinates & Size
private double _x;
public double X { ...}
private double _y;
public double Y {...}
private double _width;
public double Width {...}
private double _height;
public double Height {...}
#endregion
#region Shape Geometry
private string _shapeGeometry;
public string ShapeGeometry {...}
#endregion
#region Selection Properties
private bool _isNew;
public bool IsNew {...}
#endregion
public NoteViewModel()
{
Random random = new Random();
var notenumber = random.Next(0, 100);
var notename = "Note" + notenumber;
Name = notename;
X = 10;
Y = 10;
ShapeGeometry = "M39.967 23.133c-0.211 0.189-0.523 0.199-0.748 0.028l-7.443-5.664l-3.526 21.095c-0.013 0.08-0.042 0.153-0.083 0.219 c-0.707 3.024-4.566 5.278-9.104 5.278c-5.087 0-9.226-2.817-9.226-6.28s4.138-6.281 9.226-6.281c2.089 0 4.075 0.466 5.689 1.324 l4.664-26.453c0.042-0.242 0.231-0.434 0.475-0.479c0.237-0.041 0.485 0.068 0.611 0.28l9.581 16.192 C40.227 22.637 40.178 22.945 39.967 23.133z";
Width = CalculateSize("w");
Height = CalculateSize("h");
}
private double CalculateSize(string s) {...}
The Thumb allows for moving, and I was trying to combine the above with the example from here (without Adorners, they look awfully complicated, as all the other examples I found): http://www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part
However, after racking my brains for a considerably long time, I didn't manage to figure out a workable solution. How can I make my items resize? Please, help a damsel in distress!

WPF ComboBox selection change after switching tabs

I made a project based on nested tabs.
the nested tabs are different instance of the same viemModel and the same UI.
when I switch between the tabs he comboboxes present in the tabs chenge thei selection depending on the tab that is loosing focus.
I add both the viewmodels and the view of my test project.
thank you in advance for your help
main window
<Window.Resources>
<DataTemplate DataType="{x:Type local:IntermediateViewModel}">
<local:IntermediateView />
</DataTemplate>
<DataTemplate x:Key="HeaderedTabItemTemplate">
<Grid>
<ContentPresenter
Content="{Binding Path=Header, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" >
</ContentPresenter>
</Grid>
</DataTemplate>
<Style x:Key="SimpleTabItemStyle" TargetType="TabItem">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderThickness="1" BorderBrush="#555959">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center"
ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True" Height ="40" MinWidth ="90"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#555959" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="DefaultTabControlTemplate">
<TabControl IsSynchronizedWithCurrentItem="True"
BorderThickness="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource HeaderedTabItemTemplate}"
ItemContainerStyle="{StaticResource SimpleTabItemStyle}"
SelectionChanged="TabControl_SelectionChanged"
/>
</DataTemplate>
<!---->
</Window.Resources>
<Grid MinHeight="200" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="260*" />
<RowDefinition Height="51*" />
</Grid.RowDefinitions>
<Border >
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{DynamicResource DefaultTabControlTemplate}"
/>
</Border>
<Button Grid.Row="1" Content="Add" Command="{Binding AddCommand}"/>
</Grid>
view model (create a different istance each time)
class MainWindowViewModel : WorkspacesViewModel<IntermediateViewModel>
{
public MainWindowViewModel()
{
this.WorkspacesView.CurrentChanged += new EventHandler(WorkspacesView_CurrentChanged);
}
void WorkspacesView_CurrentChanged(object sender, EventArgs e)
{
}
RelayCommand myVar = null;
public ICommand AddCommand
{
get
{
return myVar ?? (myVar = new RelayCommand(param =>
{
SetWindow(new IntermediateViewModel("AA" + this.Workspaces.Count) );
}));
}
}
first level tab
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ClassViewModel}">
<local:ClassView />
</DataTemplate>
</UserControl.Resources>
<Border>
<ContentControl Content="{Binding Path=CurrentWorkspace, Mode=OneWay}" Loaded="ContentControl_Loaded" DataContextChanged="ContentControl_DataContextChanged" IsVisibleChanged="ContentControl_IsVisibleChanged" LayoutUpdated="ContentControl_LayoutUpdated" TargetUpdated="ContentControl_TargetUpdated" Unloaded="ContentControl_Unloaded" />
</Border>
first level viewmodel
class IntermediateViewModel : WorkspacesViewModel
{
public string Header { get; set; }
public IntermediateViewModel(string header)
{
Header = header;
SetWindow(new ClassViewModel(header));
}
}
nested tab
<UserControl.Resources>
<CollectionViewSource x:Key="StatusView" Source="{Binding Path=StatusList}"/>
</UserControl.Resources>
<Grid>
<ComboBox Name="_spl2Status" ItemsSource="{Binding Source={StaticResource StatusView}}"
SelectedValue="{Binding Path=MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="FL_TYPE"
DisplayMemberPath="ID_TYPE" Margin="76,12,0,0" Height="40" VerticalAlignment="Top" HorizontalAlignment="Left" Width="146"
DataContextChanged="_spl2Status_DataContextChanged"
IsVisibleChanged="_spl2Status_IsVisibleChanged"
Loaded="_spl2Status_Loaded"
SelectionChanged="_spl2Status_SelectionChanged"
>
</ComboBox>
</Grid>
nested tab view model
public enum myTypes
{
tipo0 = 0,
tipo1 = 1,
tipo2 = 2,
}
class ClassViewModel : WorkspaceViewModel
{
public ClassViewModel(string name)
{
Name = name;
}
public string Name { get; set; }
private List<IntEnumType> _statusList = null;
public List<IntEnumType> StatusList
{
get
{
if (_statusList == null)
_statusList = new List<IntEnumType>()
{
new IntEnumType((int)myTypes.tipo0, myTypes.tipo0.ToString()),
new IntEnumType((int)myTypes.tipo1, myTypes.tipo1.ToString()),
new IntEnumType((int)myTypes.tipo2, myTypes.tipo2.ToString()),
};
return _statusList;
}
}
private int myVar = 1;
public int MyProperty
{
get
{
return myVar;
}
set
{
if (myVar != value)
{
myVar = value;
OnPropertyChanged(() => MyProperty);
}
}
}
}
public class TabItemStyleSelector : StyleSelector
{
public Style MainTabItem { get; set; }
public Style ChildrenTabItem { get; set; }
public Style SpecificationTabItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
//if (item is IHome)
// return MainTabItem;
//else if (item is SpecificationItemViewModel)
// return SpecificationTabItem;
//else
return ChildrenTabItem;
}
}
The code is a little hard to completely follow, but I'm guessing that the issue is that there is only one instance of your ClassViewModel and it is where the selection for the combo box is stored {Binding Path=MyProperty, so whatever is stored in MyProperty will be reflected in all instances of the combo box regardless of where they live.
Well this is a bit late, but as I'm facing the same issue, I want to share my analysis.
When you change your tabs, you change the DataContext of the current Tab to your other ViewModel and hence also the ItemsSource of your ComboBox.
In case your previously selected Item (SelectedItem) is not contained within the new ItemsSource, the ComboBox fires a SelectionChanged-Event and therefore sets the SelectedIndex to -1.
Altough this default behaviour of the ComboBox might make sense, it's very annoying in many cases.
We've derived an own class from ComboBox, handling that. But it's not very satisfying as you loose some default behaviour you most probably need.
The problem is in your loaded event handlers.
When you switch tabs your unloading one tab and loading a new one.
I suspect your changing MyComboBox.SelectedIndex in _spl2Status_Loaded.

Categories