StaticResource mixed with ItemSource in TabControl - c#

I have an UserControl. At the top, there is a global parameter, bound to a static property in the class MultiSliceCommand. Below, there is a TabControl, populated by a Template and bound to public static ObservableCollection<GroupContainer> groups, also a property in MultiSliceCommand. GroupContainer contains various properties, mainly doubles, ints etc., displayed and editable in textboxes in the TabItems.
When I now change a value in TabItem, the corresponding property in the correct element of groups is set.
However, when I close & reopen the dialog, the all the GroupContainers in groups are reset to their defaults - even the properties not bound at any point to the dialog.
Changes to the global variables (outside of the TabControl) are preserved correctly. Changes to the TabControl are also preserved correctly if I remove the binding to the global variables - in explicit, if I remove the lines <local:MultiSliceCommand x:Key="mutliSliceCommand" /> and <TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
How can I change the bindings to preserve the changes to the global variable as well as the contents of the Tabs when closing & reopening the dialog?
The Xaml File:
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplate">
<Label Content="{Binding Group_Name}" />
</DataTemplate>
<local:MultiSliceCommand x:Key="mutliSliceCommand" />
<DataTemplate x:Key="ItemTemplate">
<Grid>
<TextBox x:Name="_length" Text="{Binding Path=Length, UpdateSourceTrigger=PropertyChanged, Delay=0}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox
Header="Global Parameters"
Grid.Row="0"
Grid.Column="0"
>
<Grid Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
</Grid>
</GroupBox>
<GroupBox
Header="Materials"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
>
<TabControl x:Name="TabControl1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplate="{StaticResource HeaderTemplate}"
ContentTemplate="{StaticResource ItemTemplate}"
/>
</GroupBox>
<!--
<Button Content="Save settings"
Grid.Row="2"
HorizontalAlignment="Right"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="75"
Click="Btn_Save" />-->
</Grid>
</ScrollViewer>
The Class MultiSliceCommand
public class MultiSliceCommand
{
public static ObservableCollection<GroupContainer> groups { get; set; }
private static double _mm_per_package { get; set; } = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public MultiSliceCommand()
{
groups = new ObservableCollection<GroupContainer>
{
new GroupContainer("Group 1"),
new GroupContainer("Group 1"),
new GroupContainer("Group 3")
};
}
}
The class ObjectContainer
public class GroupContainer : INotifyPropertyChanged
{
private double _length { get; set; } = 0;
public double Length
{
get { return _length; }
set { _length = value < 0 ? 0 : value; NotifyPropertyChanged("Min_Vector_Length"); }
}
// Methods
public GroupContainer(string group_name)
{
}
// Helper Stuff
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
}

Ok, fixed it with an (somewhat dirty) hack:
I just outsourced the global variable to its own class, and bind the xaml to this class. In MultiSliceCommand, I use getter / setter on the property to just relay the value from the "isolation class"
Isolation class:
public class xaml_backend_variables
{
private static double _mm_per_package = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public xaml_backend_variables()
{
}
}
MultiSliceCommand
public static double Mm_Per_Package
{
get { return xaml_backend_variables.Mm_Per_Package; }
set { xaml_backend_variables.Mm_Per_Package = value; }
}
XAML Modifications
....
<local:xaml_backend_variables x:Key="xaml_backend_variables" />
....
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource xaml_backend_variables}, Path=Mm_Per_Package}" />
But now all values are preserved correctly when closing and reopening the dialog.
Still, if someone has an explanation why this happens and what would be the correct / elegant way to solve this, I would like very much to know!

Related

Empty Rows in WPF DataGrid

I have both a DataGrid and a ComboBox on wpf UserControl.
namespace ApSap
{
public partial class DocumentView : UserControl
{
public Document document;
public DocumentView(Document selectedDoc)
{
document = selectedDoc;
InitializeComponent();
DocBrowser.Navigate(document.FilePath);
// shows only empty rows
SapGrid.ItemsSource = document.SapDocNumbers;
// shows list of values correctly
Combo.ItemsSource = document.SapDocNumbers;
}
}
}
The combo Box correctly displays the content of the public property "SapDocNumbers" (a list of integers),
However the datagrid only displays empty rows, albiet the correct number of them.
the XAML is as follows:
<UserControl x:Class="ApSap.DocumentView"
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"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
>
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Grid.Row="1">
<DataGrid AutoGenerateColumns="True" x:Name="SapGrid" Margin="10,10,10,10" >
</DataGrid>
<Button x:Name="CreateInvoice" Content="Create Invoice" Margin="10,10,10,10" />
<Button x:Name="Save" Content="Save and Exit" Margin="10,10,10,10" />
<ComboBox x:Name="Combo" Margin="10,10,10,10" />
</StackPanel>
</Grid>
Is there anything I am missing from XAML grid definition that would mean the combo works correctly, but the datagrid does not?
as requested here is the definition of the class:
public class Document : INotifyPropertyChanged
{
private int _docID;
private List<Int64> _sapDocNumbers;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int DocID
{
get { return _docID; }
set
{
if (value != _docID)
{
_docID = value;
NotifyPropertyChanged();
}
}
}
public List<Int64> SapDocNumbers
{
get { return _sapDocNumbers; }
set
{
if (value != _sapDocNumbers)
{
_sapDocNumbers = value;
NotifyPropertyChanged();
}
}
}
Thank you
The answer to your question depends on the implementation of the collection item type.
ComboBox uses ToString () by default to represent the item.
And DataGrid renders the properties of the element - for each property there is a separate column.
If the element type has no properties, the DataGrid will not create columns and will not display anything.
For a more precise answer, show the type of the collection and the implementation of the type of its element.
Completion of the answer in connection with the clarification of the question:
You want to display the ulong collection.
This type has no properties and therefore autogenerating columns in the DataGrid cannot create columns.
For your task you need:
<DataGrid AutoGenerateColumns="False" x:Name="SapGrid" Margin="10" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Mode=OneWay}" Header="Number"/>
</DataGrid.Columns>
</DataGrid>
one final question, How do i get a blank row on the DataGrid so the user can add their own SapDocNumbers to the list ?
The item of the collection goes to the Data Context of the DataGrid row.
UI elements cannot edit their DataContext.
Therefore, for a string, you need to create a reference ty with a property of the desired type and edit this property.
And this type must have a default constructor.
Since the list can be used in several bindings, the type of the list should not be a simple collection, but an observable collection.
For examples used class BaseInpc
using Simplified;
namespace ApSap
{
public class DocumentRow : BaseInpc
{
private long _sapDocNumber;
public long SapDocNumber { get => _sapDocNumber; set => Set(ref _sapDocNumber, value); }
public DocumentRow(long sapDocNumber) => SapDocNumber = sapDocNumber;
public DocumentRow() { }
}
}
using Simplified;
using System.Collections.ObjectModel;
namespace ApSap
{
public class Document : BaseInpc
{
private int _docID;
public int DocID { get => _docID; set => Set(ref _docID, value); }
public ObservableCollection<DocumentRow> SapDocNumbers { get; }
= new ObservableCollection<DocumentRow>();
public Document()
{
}
public Document(int docID, params long[] sapDocNumbers)
{
DocID = docID;
foreach (var number in sapDocNumbers)
SapDocNumbers.Add(new DocumentRow(number));
}
public static Document ExampleInstance { get; } = new Document(123, 4, 5, 6, 7, 8, 9, 0);
}
}
<Window x:Class="ApSap.DocumentWindow"
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:ApSap"
mc:Ignorable="d"
Title="DocumentWindow" Height="450" Width="800"
DataContext="{x:Static local:Document.ExampleInstance}">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Grid.Row="1">
<TextBlock Margin="10,10,10,0">
<Run Text="DocID:"/>
<Run Text="{Binding DocID}"/>
</TextBlock>
<DataGrid AutoGenerateColumns="True" Margin="10"
ItemsSource="{Binding SapDocNumbers}"/>
<Button x:Name="CreateInvoice" Content="Create Invoice" Margin="10" />
<Button x:Name="Save" Content="Save and Exit" Margin="10" />
<ComboBox x:Name="Combo" Margin="10" />
</StackPanel>
<DataGrid Grid.Row="1" AutoGenerateColumns="True" Margin="10"
ItemsSource="{Binding SapDocNumbers}" VerticalAlignment="Top"/>
</Grid>
</Window>
P.S. Learn to set the Data Context and bindings to it. Using the names of UI elements, referring to them in Sharp is most often very bad code.

Fire control event when other control event is fired

This is my first time writing here. I though my first question would by more complex but I am tired of searching for an answer.
I just started with WPF (MVVM), here it goes:
I have 3 user Controls in a Page, the three of them are the same class of Control. The important thing here is that in the first TextBox, when it lost focus, it calls a method to calculate the last TextBox.Text.
<UserControl x:Class="ASSEMBLY.View.UserControls.EtiquetaDinamicaUserControl"
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:local="clr-namespace:ASSEMBLY.View.UserControls"
x:Name="userControl"
mc:Ignorable="d"
Background="Orange" BorderThickness="0" >
<DockPanel VerticalAlignment="Stretch">
<Grid Background="green">
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Viewbox Grid.Column="0" VerticalAlignment="Stretch" Stretch="Fill" >
<Label Grid.Column="0" Content="{Binding NombreEtiqueta, ElementName=userControl, Mode=TwoWay}" HorizontalAlignment="Stretch" BorderThickness="0" Margin="0,0,5,0"
Background="White" VerticalAlignment="Stretch" />
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Stretch" Stretch="Fill" >
<TextBox Grid.Column="1" MinWidth="30" BorderThickness="0" Margin="0,0,5,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Left" LostFocus="IdChanged" Loaded="IdChanged"
Text="{Binding IdValue, ElementName=userControl, Mode=TwoWay}"
/>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Stretch" Stretch="Fill" >
<TextBox Grid.Row="0" HorizontalAlignment="Stretch" IsEnabled="True" MinWidth="100" BorderThickness="0"
TextAlignment="Left" VerticalAlignment="Stretch"
Text="{Binding Path= Descripcion, ElementName=userControl, Mode=TwoWay}">
</TextBox>
</Viewbox>
</Grid>
</DockPanel>
I have 3 times the same control in the Page, now I need that when the UserControl2 fires LostFocus also fire the LostFocus of the usercontrol3.
<controls:EtiquetaDinamicaUserControl Grid.Row="0" Grid.Column="0" Margin="5,2,0,0" IdValue="{Binding Codempresa1, Mode=TwoWay}" NombreEtiqueta="TEST1"/>
<controls:EtiquetaDinamicaUserControl x:Name="UserControl2" Grid.Row="1" Grid.Column="0" Margin="5,2,0,0" IdValue="{Binding Codempresa2, Mode=TwoWay}" NombreEtiqueta="TEST2"/>
<controls:EtiquetaDinamicaUserControl x:Name="UserControl3" Grid.Row="2" Grid.Column="0" Margin="5,2,0,2" IdValue="{Binding Codempresa3, Mode=TwoWay}" NombreEtiqueta="TEST3"/>
I searched everywhere for something similar, but I found eventrigger (not working because I am not changing a property), interaction (no success trying it).
It works if I set the TextChangedproperty instead of Lostfocus because everything it's bound, but I don't want to be calculating the second textbox every character the user input.
Sorry for my english mistakes and thank you for your help
EDITED. IN ORDER TO HELP I COPY THE CODE BEHIND OF THE USERCONTROL AND THE MVVM PART OF THE VIEW.
USERCONTROL CODE BEHIND:
public static readonly DependencyProperty nombreEtiqueta =
DependencyProperty.Register("NombreEtiqueta", typeof(string), typeof(EtiquetaDinamicaUserControl), new
PropertyMetadata("DEF"));
public string NombreEtiqueta
{
get { return (string)GetValue(nombreEtiqueta); }
set { SetValue(nombreEtiqueta, value); }
}
public static readonly DependencyProperty SetDescripcionProperty =
DependencyProperty.Register("Descripcion", typeof(string), typeof(EtiquetaDinamicaUserControl), new
PropertyMetadata("SIN ASIGNAR"));
public string Descripcion
{
get { return (string)GetValue(SetDescripcionProperty); }
set { SetValue(SetDescripcionProperty, value); }
}
public static DependencyProperty SetIdValueProperty =
DependencyProperty.Register("IdValue", typeof(string), typeof(EtiquetaDinamicaUserControl), new
PropertyMetadata("0"));
public string IdValue
{
get { return (string)GetValue(SetIdValueProperty); }
set { SetValue(SetIdValueProperty, value);
}
}
#region Evento al perder foco IdValue
private void IdChanged(object sender, RoutedEventArgs e)
{
try
{
int tmp_id = Convert.ToInt32(((TextBox)e.Source).Text);
if (tmp_id != 0)
{
Descripcion = tmp_id.ToString();
}
}
catch
{
Descripcion = "SOLO NUMEROS";
}
}
#endregion
MVVM from the View
class PaginaReservaViewModel:ViewModelBase
{
Reserva reserva;
#region Atributos Agencias
private int codempresa1;
private int codempresa2;
private int codempresa3;
#endregion
#region Get/Set Agencias
public int Codempresa1 { get { return codempresa1; } set { codempresa1 = value; } }
public int Codempresa2
{
get { return codempresa2; }
set
{
codempresa2 = value;
if (codempresa3 == 0)
{
codempresa3 = codempresa2;
OnPropertyChanged("Codempresa3");
}
}
}
public int Codempresa3 {
get { return codempresa3; }
set {
codempresa3 = value; } }
Can you use UpdateSourceTrigger="LostFocus" in UserControl' TextBox's Text binding? so it wont fire on every key input. Also please try to implement this all with MVVM (Without hooking events in Code Behind), then you will have much control over the operation.
My Suggestion:
In ViewModel implement a way to modify the other values, when changing Codempresa1. You can include a method to calculate them and call the method in set method of Codempressa1 or you can include the method in PropertyChanged handler.

Define command binding in user control

I wrote user control with 2 buttons and one check box and now I want to bind Commands to data context - for each button and checkbox.
But I don't know how to define command binding. I think I'll need some kind of ICommand property in User control - but how can I connect user's data context command delegate? I want to use user control to manage each item in collection like this:
<ItemsControl ItemsSource="{Binding Path=MoneyInfo}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ChannelSetupControl
CurrentCount="{Binding Count}"
CoinValue="{Binding Value}"
UpCommand="{Binding DataContextUp}"
DownCommand="{Binding DataContextDown}"
ChangeCheckboxCommand="{Binding DataContextChange}"></local:ChannelSetupControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
XAML User control
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="3*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Text="{Binding CoinValue}" TextAlignment="Center"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding CurrentCount, Mode=TwoWay}" TextAlignment="Center" VerticalAlignment="Center" FontSize="30"></TextBlock>
<StackPanel Grid.Column="1" Grid.Row="1" VerticalAlignment="Center">
<Button Content="+ 10" Padding="0 5"></Button>
<Button Content="- 10" Padding="0 5"></Button>
</StackPanel>
<CheckBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" IsChecked="{Binding Cycling, Mode=TwoWay}" Content="recycling" VerticalContentAlignment="Center"></CheckBox>
</Grid>
</UserControl>
and code behind and this is where I'm lost - how to define UpCommand, DownCommand and ChangeCheckboxCommand?
public partial class ChannelSetupControl : UserControl, INotifyPropertyChanged
{
private int currentCount;
private bool cycling;
private double coinValue;
public int Step { get; set; }
public double CoinValue { get { return coinValue; } set { coinValue = value; NotifyPropertyChanged("CoinValue"); } }
public int CurrentCount { get { return currentCount; } set { currentCount = value; NotifyPropertyChanged("CurrentCount"); } }
public bool Cycling { get { return cycling; } set { cycling = value; NotifyPropertyChanged("Cycling"); } }
public ChannelSetupControl()
{
InitializeComponent();
DataContext = this;
CurrentCount = 0;
Step = 10;
Cycling = false;
CoinValue = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
First of all your ChannelSetupControl class extends UserControl, so it implicitly extends DependencyObject class. It means you can use Dependency Properties instead of implementing INotifyPropertyChanged.
So you can define a dependency property in your ChannelSetupControl class, like this one:
public static readonly DependencyProperty UpCommandProperty =
DependencyProperty.Register("UpCommand", typeof(ICommand), typeof(ChannelSetupControl));
public ICommand UpCommand
{
get { return (ICommand)GetValue(UpCommandProperty); }
set { SetValue(UpCommandProperty, value); }
}
At the same time in your control XAML:
<Button Command="{Binding RelativeSource={RelativeSource Mode=Self}, Path=UpCommand, Mode=OneWay}"
Content="+ 10" Padding="0 5" />
In this way in your window XAML you can wrote:
<local:ChannelSetupControl UpCommand="{Binding UpCommand, Mode=OneWay}" ... />
You can use the same "pattern" for the other controls.
Regarding ICommand, there are a lot of implementations. The one that I prefer is the so called delegate command (for a sample you can take a look here).
I hope this quick explanation can help you.

Failing To Bind Dynamically Generated Buttons & Textboxes in WPF

I was working on dynamic generation of labels, buttons and Textbox in my WPF application. Well I was successful in dynamically creating them but I am facing one major issue in it.
Xaml:
<ListBox x:Name="myViewChannelList" HorizontalAlignment="Stretch" Height="Auto" ItemsSource="{Binding}" Margin="0" VerticalAlignment="Stretch" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" Margin="50,20,0,0"></Label>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=VoltageText}" Height="25" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
<Button Grid.Column="1" Content="Set" Height="25" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" ></Button>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Model Class:
private string _ChanelName = "";
public String ChanelName
{
get
{
return _ChanelName;
}
set
{
if (value != _ChanelName)
{
_ChanelName = value;
OnPropertyChanged("ChanelName");
}
}
}
// Constructor
public VoltageModel(string ChanelName)
{
this.ChanelName = ChanelName;
}
public override string ToString()
{
return _ChanelName;
}
ViewModel Class:
class ChannelList : ObservableCollection<VoltageModel>, INotifyPropertyChanged
{
private string _VoltageText;
public string VoltageText
{
get { return _VoltageText; }
set
{
_VoltageText = value;
OnPropertyChanged("VoltageText");
}
}
// Method gets called when Set Button Is Clicked
public void SetCommandExecuted()
{
string val = VoltageText;
}
//Notify Property Changed members are present
}
Xaml.cs Class:
ChannelList myChanels = new ChannelList();
public VoltageView() // Constructor
{
InitializeComponent();
myChanels.Add(new VoltageModel("VDD__Main"));
myChanels.Add(new VoltageModel("VDD__IO__AUD"));
myChanels.Add(new VoltageModel("VDD__CODEC__AUD"));
myViewChannelList.DataContext = myChanels;
}
This gives me 3 Labels(Content as above), 3 textboxes and 3 buttons when I run the application.
Now when I enter the value inside the textbox it shows null on button click when I put a breakpoint in SetCommandExecuted(). Most importantly any of the 4 button I click generates the event. I want the first textbox and first button to be in sync(bind), 2nd textbx and 2nd button to be in sync and so on. Basically each control must be in sync with the other control in a row. It should not effect the other rows. Is it possible???
Here is the solution to your question. As general practice you want to avoid all logic, building your data, etc. in the code behind. All the business logic should be in the view model which will make it easier to unit test.
Here is the view
.xaml
<StackPanel>
<ListBox HorizontalAlignment="Stretch"
Height="Auto"
ItemsSource="{Binding VoltageCollection}"
VerticalAlignment="Stretch"
Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Width="100"
Content="{Binding ChannelName}" />
<TextBox Width="100"
Text="{Binding VoltageText}" />
<Button Margin="10,0,0,0"
Content="Set"
Command="{Binding VoltageCommand}"
CommandParameter="{Binding VoltageText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the code behind
.xaml.cs
private ChannelListViewModel m_voltageViewModel;
public MainWindow()
{
InitializeComponent();
m_voltageViewModel = new ChannelListViewModel();
m_voltageViewModel.Initialize();
DataContext = m_voltageViewModel;
}
Here is the Model: VoltageModel
public class VoltageModel : INotifyPropertyChanged
{
public string ChannelName { get; set; }
private string m_voltageText;
public string VoltageText
{
get { return m_voltageText; }
set
{
m_voltageText = value;
OnPropertyChanged("VoltageText");
}
}
public ICommand VoltageCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the ViewModel: ChannelListViewModel
public class ChannelListViewModel
{
private ICommand m_voltageCommand;
public ChannelListViewModel()
{
m_voltageCommand = new DelegateCommand(x => SetCommandExecute(x));
}
public void Initialize()
{
VoltageCollection = new ObservableCollection<VoltageModel> { new VoltageModel() { ChannelName = "VDD__Main", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__IO__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__CODEC__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand }};
}
public ObservableCollection<VoltageModel> VoltageCollection { get; set; }
public void SetCommandExecute(object voltageText)
{
Debug.WriteLine(voltageText);
}
}
Finally simple DelegateCommand class DelegateCommand
public class DelegateCommand : ICommand
{
Action<object> m_executeDelegate;
public DelegateCommand(Action<object> executeDelegate)
{
m_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
m_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
i didn't get much into what was wrong since i recognized 2 things that were generally very wrong ,and they might be the problem for unexpected behavior on your part .
the first : your DataTemplate places your control one on top of the other .
fix :
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" />
<TextBox Grid.Column="1" Text="{Binding Path=VoltageText}" />
<Button Grid.Column="2" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" />
</Grid>
</DataTemplate>
the second : your Properties are set after PropertyChanged event was risen so they would not be updated until the next time you input a value.
fix :
private T _property;
public T Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged("Property");
}
}
make these fixes and edit your post if you still have issues post a comment under my answer.

WPF Listview + GridView - View not Updating

I have got a Listview Item with a Gridview Child, bound to a List of Objects.
Below the Gridview I have got texboxes to edit the content of the Gridview (bound to the Gridview).
I can add new content (which is displayed in the GridView).
When i edit content, it is in fact edited (in the object list) but not displayed in the Gridview (the GridView does not seem to update)
xaml code:
<!-- ========= -->
<!-- root Grid -->
<!-- ========= -->
<Grid x:Name="root" Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- ========= -->
<!-- Data Grid -->
<!-- ========= -->
<ListView x:Name="dataGrid" Grid.Row="0" Grid.ColumnSpan="2" ItemsSource="{Binding}">
<ListView.View>
<GridView>
<!-- first solution -->
<GridViewColumn x:Name="gridColumnName" Header="Name" Width="160">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- second solution -->
<GridViewColumn x:Name="gridColumnPath" Header="Path" DisplayMemberBinding="{Binding Path=Path}" Width="490" />
</GridView>
</ListView.View>
</ListView>
<!-- ========= -->
<!-- Edit Menu -->
<!-- ========= -->
<Label Content="Name:" Grid.Row="1" Grid.Column="0" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<TextBox x:Name="txtBoxName" Grid.Row="1" Grid.Column="1" Width="250" VerticalAlignment="Bottom" HorizontalAlignment="Left"
DataContext="{Binding ElementName=dataGrid, Path=SelectedItem}"
Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="Path:" Grid.Row="2" Grid.Column="0" VerticalAlignment="Bottom" HorizontalAlignment="Left" />
<TextBox x:Name="txtBoxPath" Grid.Row="2" Grid.Column="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch"
DataContext="{Binding ElementName=dataGrid, Path=SelectedItem}"
Text="{Binding Path=Path, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Object List class:
class ItemList : ObservableCollection<LdapItem>
{
public ItemList()
: base()
{
}
}
Object class:
class LdapItem : INotifyPropertyChanged
{
#region constructor
public LdapItem(String name, String path)
{
this.iD = Guid.NewGuid().ToString();
this.name = name;
this.path = path;
}
#endregion
#region public proterties
public String ID
{
get { return iD; }
}
public String Name
{
get { return name; }
set { name = value; }
}
public String Path
{
get { return path; }
set { path = value; }
}
#endregion
#region public methods
public void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
#endregion
#region private variables
private String name = String.Empty;
private String path = String.Empty;
private String iD = String.Empty;
#endregion
public event PropertyChangedEventHandler PropertyChanged;
}
any ideas why updating the GridView doesnt work?
If you have a number of models use a base class that implements INPC. Then change your property changed event handler to:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This will eliminate the need to specify the model property being changed. Reduces the number of misspelling errors or forgetting to put the name in. Still needs to call this.OnPropertyChanged() though which you are missing in several setters.
The ItemList class doesn't make sense. It could be replaced with:
public ObservableCollection<LdapItem> LdapItems
it seems like you forgot to fire the OnPropertyChangedEvent when your property changes:
public String Name
{
get { return name; }
set {
name = value;
OnPropertyChanged("Name");
}
}
If you don't fire the PropertyChanged event, WPF will not be able to see if the object has changed.

Categories