WPF: ComboBox SelectedIndex = -1? - wpf

I am using a MVVM Wizard with several pages. When I set a value in the combobox and go to the next page and switch back I want to reset the value I set before.
But all whats happening is that the combobox is empty at top and the index is -1 ?
What do I wrong?
<ComboBox ItemsSource="{Binding Path=LessonNumbers}" SelectedIndex="{Binding SelectedLessonNumber}" />
private ReadOnlyCollection<int> _lessonNumbers;
public ReadOnlyCollection<int> LessonNumbers
{
get
{
if (_lessonNumbers == null)
this.CreateLessonNumbers();
return _lessonNumbers;
}
}
private void CreateLessonNumbers()
{
var list = new List<int>();
for (int i = 1; i < 24; i++)
{
list.Add(i);
}
_lessonNumbers = new ReadOnlyCollection<int>(list);
}
private int _selectedLessonNumber;
public int SelectedLessonNumber
{
get { return _selectedLessonNumber; }
set
{
if (_selectedLessonNumber == value)
return;
_selectedLessonNumber = value;
this.OnPropertyChanged("SelectedLessonNumber");
}
}
UPDATE:
<ComboBox
SelectedIndex="0"
SelectedItem="{Binding SelectedWeeklyRotationNumber}"
ItemsSource="{Binding Path=WeeklyRotationNumbers}"
Height="23"
HorizontalAlignment="Left"
Margin="336,212,0,0"
VerticalAlignment="Top"
Width="121"
MaxDropDownHeight="100"
IsReadOnly="True"
IsTextSearchEnabled="False"
/>
private ReadOnlyCollection _weeklyRotationNumbers;
public ReadOnlyCollection WeeklyRotationNumbers
{
get
{
if (_weeklyRotationNumbers == null)
this.CreateWeeklyRotationNumbers();
return _weeklyRotationNumbers;
}
}
private void CreateWeeklyRotationNumbers()
{
var list = new List<string>();
list.Add("No rotation");
for (int i = 1; i < 16; i++)
list.Add(i.ToString());
_weeklyRotationNumbers = new ReadOnlyCollection<string>(list);
}
private string _selectedWeeklyRotationNumber;
public string SelectedWeeklyRotationNumber
{
get { return _selectedWeeklyRotationNumber; }
set
{
if (_selectedWeeklyRotationNumber == value)
return;
_selectedWeeklyRotationNumber = value;
this.RaisePropertyChanged("SelectedWeeklyRotationNumber");
Messenger.Default.Send<string>(value);
}
}
Again, what do I wrong or what is wrong with the string property?

Change XAML SelectedIndex to SelectedItem:
<ComboBox ItemsSource="{Binding Path=LessonNumbers}"
SelectedItem="{Binding SelectedLessonNumber}" />
UPDATE:
Somewhere you must set the DataContext of your Window to reference the collection from your XAML.
In my case I typically do that in the constructor of my view.
// this my class containing WeeklyRotationNumbers
private MainViewModel _mvm;
public MainView()
{
InitializeComponent();
_mvm = new MainViewModel();
DataContext = _mvm;
}
I added string to the read only collections:
private ReadOnlyCollection<string> _weeklyRotationNumbers;
public ReadOnlyCollection<string> WeeklyRotationNumbers
I also implemented the interface INotifyPropertyChanged which I think you did, but you are likely using
a different base class to handle the PropertyChanged event.
Everthing else I cut and paste from your code.

Related

wpf ListItem SelectedValue Object is always null

I have a simple ListBox and a TextBox as under.I want to display the selectedvalue property of Listbox in the textbox,but my ViewModel's selected object is always null.
What am i missing here?
My XAML
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding CurrentRec.Name,Mode=OneWay}" />
<ListBox x:Name="AllMatching" Width="{Binding ElementName=TxtMail,Path=Width}" Height="100" Canvas.Top="54" Canvas.Left="36" DisplayMemberPath="Name" SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedValue="Name" SelectedValuePath="Name" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
My ViewModel:
public class VM_Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public List<DM_Data> AllData;
public DM_Data CurrentRec;
public VM_Data()
{
LoadData();
}
public int ID
{
get { return p_ID; }
set
{
if (p_ID != value)
{
RaisePropertyChangedEvent("ID");
p_ID = value;
}
}
}
public double SP
{
get { return p_SP; }
set
{
if (p_SP != value)
{
RaisePropertyChangedEvent("SP");
p_SP = value;
}
}
}
public double CP
{
get { return p_CP; }
set
{
if (p_CP != value)
{
RaisePropertyChangedEvent("CP");
p_CP = value;
}
}
}
public string Name
{
get { return p_Name; }
set
{
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
}
}
private void LoadData()
{
AllData = new List<DM_Data>();
string[] strNames = "Jatinder;Shashvat;shashikala;shamsher;shahid;justin;jatin;jolly;ajay;ahan;vijay;suresh;namita;nisha;negar;zenith;zan;zen;zutshi;harish;hercules;harman;ramesh;shashank;mandeep;aman;amandeep;amarjit;asim;akshay;amol;ritesh;ritivik;riz;samana;samaira;bhagwandass;bhagwan;bhawna;bhavna".Split(';');
for(int i=0;i<=strNames.GetUpperBound(0);i++)
{
DM_Data NewRec = new DM_Data();
NewRec.CP = new Random().Next(200, 400);
NewRec.SP = new Random().Next(1, 10);
NewRec.ID = i + 1;
NewRec.Name = strNames[i];
AllData.Add(NewRec);
}
AllData = AllData.OrderBy(item => item.Name).ToList();
}
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
My DataModel
public class DM_Data
{
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public int ID
{
get { return p_ID; }
set { p_ID = value; }
}
public double SP
{
get { return p_SP; }
set { p_SP = value; }
}
public double CP
{
get { return p_CP; }
set { p_CP = value; }
}
public string Name
{
get { return p_Name; }
set { p_Name = value; }
}
MainWindow.Xaml.cs
public partial class MainWindow : Window
{
VM_Data ViewModel;
public MainWindow()
{
InitializeComponent();
ViewModel = new VM_Data();
this.DataContext = ViewModel;
AllMatching.ItemsSource = ViewModel.AllData;
}
private void cmdtest_Click(object sender, RoutedEventArgs e)
{
DM_Data crec = ViewModel.CurrentRec;
}
}
CurrentRec must be a property that raises the PropertyChanged event:
private DM_Data _currentRec;
public DM_Data CurrentRec
{
get { return _currentRec; }
set { _currentRec = value; RaisePropertyChangedEvent("CurrentRec"); }
}
In the code you have posted, it is a field and you cannot bind to fields:
public DM_Data CurrentRec;
You can't bind to fields! CurrentRec must be a property. At now it is a field.
Why do you set ItemsSource in code-behind? Set it in XAML.
You should call RaisePropertyChangedEvent after you've changed backing field, not before.
It is not right pattern for events raising: if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(Property)); } You need to save event delegate to variable first or use ?.Invoke.
Don't create new instances of Random on every iteration of loop because you will get equal values. Create the only one outside of the loop and use it.
What you are doing is kind of MVVM, but not really.
Here is a quick fix anyway:
Please take a look at the Bindings.
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}" />
<ListBox x:Name="AllMatching"
Width="{Binding ElementName=TxtMail,Path=Width}"
Height="100"
Canvas.Top="54"
Canvas.Left="36"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
</StackPanel>
I think you get the idea at: Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}".
Aditional Information
First:
You fire to early dude. Please first assign the value and then say its changed.
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
Second:
Use a ObservableCollection<DM_Data> to let your ListBox know about changes.
Third:
Use the posibility of Binding
Remove AllMatching.ItemsSource = ViewModel.AllData; and go like
<ListBox x:Name="AllMatching"
ItemsSource="{Binding Path=AllData}"
...
/>
And after all of this - please man check out some tutorials. And also refactor your code from VM_Data to DataViewModel thank you sir.

How to determine Panorama item index when selected panorama item is changed

I am building a panorama which displays images through binding. I need to find index of panorama item whenever the current item changes. But the SELECTIONCHANGED event is not firing in case when data is retrieved through binding. Can you please suggest some other way. Thanx in advance
XAML Code
<phone:Panorama x:Name="HeaderPanorama"
ItemsSource="{Binding PanoramaImages}"
Width="550" Margin="-10,-255,0,-140"
SelectionChanged="HeaderPanorama_SelectionChanged_1">
<phone:Panorama.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Margin="-10"/>
</DataTemplate>
</phone:Panorama.ItemTemplate>
</phone:Panorama>
CodeBehind
private void HeaderPanorama_SelectionChanged_1(
object sender,
SelectionChangedEventArgs e)
{
if (this.DataContext != null && this.DataContext is HomeViewModel)
{
((HomeViewModel)this.DataContext).PanoramaItemIndex =
HeaderPanorama.SelectedIndex;
}
}
ViewModel Code
public HomeViewModel()
{
RequestHomeData();
PanoramaImages = new List<string>();
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
}
private List<string> _panoramaImages;
public List<string> PanoramaImages
{
get { return _panoramaImages; }
set
{
_panoramaImages = value;
NotifyPropertyChanged("PanoramaImages");
}
}
private int _panoramaItemIndex;
public int PanoramaItemIndex
{
get { return _panoramaItemIndex; }
set
{
_panoramaItemIndex = value;
NotifyPropertyChanged("PanoramaItemIndex");
}
}

How to set selected item of a DataGrid programmatically in WPF with MVVM application?

I have bound the DataTable to the DataGrid control. How can I set the selected item programmatically ?
Example
In my view model I have a property of type DataTable to bind the DataGrid
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
My XAML
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,120" />
The constructor of the view model (assigning dummy values)
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
this.SizeQuantityTable.Columns.Add(sizeQuantityColumn);
DataColumn sColumn = new DataColumn();
sColumn.ColumnName = "S";
this.SizeQuantityTable.Columns.Add(sColumn);
DataColumn mColumn = new DataColumn();
mColumn.ColumnName = "M";
this.SizeQuantityTable.Columns.Add(mColumn);
DataRow row1 = this.SizeQuantityTable.NewRow();
row1[sizeQuantityColumn] = "Blue";
row1[sColumn] = "12";
row1[mColumn] = "15";
this.SizeQuantityTable.Rows.Add(row1);
DataRow row2 = this.SizeQuantityTable.NewRow();
row2[sizeQuantityColumn] = "Red";
row2[sColumn] = "18";
row2[mColumn] = "21";
this.SizeQuantityTable.Rows.Add(row2);
DataRow row3 = this.SizeQuantityTable.NewRow();
row3[sizeQuantityColumn] = "Green";
row3[sColumn] = "24";
row3[mColumn] = "27";
this.SizeQuantityTable.Rows.Add(row3);
OK. I have created three columns namely sizeQuantityColumn, sColumn and mColumn and added three rows namely row1, row2 and row2.
So, Let's say I wanna set the selected item as row2 (So in the view, the second row should be highlighted).
How can I do this?
EDIT
I hardcoded the SelectedIndex of the DataGrid to 1. (So the second row should be selected). In design time it shows as selected. But not in the run time. You can see it in the below snapshot.
So ultimaltely the problem is Not highlighting the row.
There are a few way to select items in the DataGrid. It just depends which one works best for the situation
First and most basic is SelectedIndex this will just select the Row at that index in the DataGrid
<DataGrid SelectedIndex="{Binding SelectedIndex}" />
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
SelectedIndex = 2;
SelectedItem will select the row that matches the row you set
<DataGrid SelectedItem="{Binding SelectedRow}" />
private DataRow _selectedRow;
public DataRow SelectedRow
{
get { return _selectedRow; }
set { _selectedRow = value; NotifyPropertyChanged("SelectedRow");}
}
SelectedRow = items.First(x => x.whatever == something);
The most common one is SelectedValue with SelectedValuePath set, in this case you set the column you want to select with and then to can select the row by setting the corresponding value
<DataGrid SelectedValuePath="Size Quantity" SelectedValue="{Binding SelectionValue}"
private string _selectedValue
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
SelectionValue = "Blue";
Edit:
Here is my test and it is highlighting just fine
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
...................
........
}
private string _selectedValue;
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get { return sizeQuantityTable; }
set { sizeQuantityTable = value; NotifyPropertyChanged("SizeQuantityTable"); }
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
SelectedIndex = 2;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
SelectionValue = "Blue";
}
private void NotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
Xaml:
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="202" Width="232" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<DataGrid SelectedValuePath="Size Quantity"
SelectedValue="{Binding SelectionValue}"
SelectedIndex="{Binding SelectedIndex}"
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,41" />
<StackPanel Orientation="Horizontal" Height="37" VerticalAlignment="Bottom" >
<Button Content="SelectedIndex" Height="26" Width="107" Click="Button_Click_1"/>
<Button Content="SelectedValue" Height="26" Width="107" Click="Button_Click_2"/>
</StackPanel>
</Grid>
</Window>
Result:
You can always use SelectedItem property and bind it against row, as such:
SelectedItem="{Binding ActiveRow}"
and in ViewModel do:
ActiveRow = secondRow;
Add SelectedItem, SelectedValue in your DataGrid.
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
SelectedValue ="{Binding SelectedValue}"
Margin="0,0,0,120" />
And in your view model
private string _selectedValue;
public string SelectedValue
{
get
{
return _selectedValue;
}
set
{
_selectedValue = value;
NotifyPropertyChanged("SelectedValue");
}
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
You can use SelectedItem as well, that is preferred.
I just had the same problem.
I saw that the item of a datagrid was selected correctly at design time, but not at runtime. (By the way, I create the instance of the view model in the xaml).
I could solve this problem by moving the code to programmatically set the selected item from the view models constructor to a different method in the view model and then calling this method in the loaded event of the window (or usercontrol).
Obviously the view is not completely done initializing itself when the view models constructor is called.
This can be avoided by not coding in the view models constructor.
View (xaml):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels" Loaded="Window_Loaded">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
View (code behind):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
((ExampleViewModel)this.DataContext).LoadData();
}
If you do not like setting up the Loaded event in the code behind, you can also do it in xaml (references to "Microsoft.Expression.Interactions" and "System.Windows.Interactivity" are needed):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In each case, you call the LoadData method in the ViewModel:
public class ExampleViewModel
{
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
// Do NOT set selected item here
}
public void LoadData()
{
// Set selected item here
}
For anyone using Observable Collections you may find this solution useful.
The SelectedModelIndex property simply returns the index of the SelectedModel from the ItemSource collection. I've found setting the SelectedIndex along with the SelectedItem highlights the row in the DataGrid.
private ObservableCollection<Model> _itemSource
public ObservableCollection<Model> ItemSource
{
get { return _itemSource; }
set
{
_itemSource = value;
OnPropertyChanged("ItemSource");
}
}
// Binding must be set to One-Way for read-only properties
public int SelectedModelIndex
{
get
{
if (ItemSource != null && ItemSource.Count > 0)
return ItemSource.IndexOf(SelectedModel);
else
return -1;
}
}
private Model _selectedModel;
public Model SelectedModel
{
get { return _selectedModel; }
set
{
_selectedModel = value;
OnPropertyChanged("SelectedModel");
OnPropertyChanged("SelectedModelIndex");
}
}
xaml:
<DataGrid SelectionUnit="FullRow" >
</DataGrid>
code
SelectRowByIndex(yourDataGridViewName, 0);
public static void SelectRowByIndex(DataGrid dataGrid, int rowIndex)
{
if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.FullRow))
throw new ArgumentException("The SelectionUnit of the DataGrid must be set to FullRow.");
if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));
dataGrid.SelectedItems.Clear();
/* set the SelectedItem property */
object item = dataGrid.Items[rowIndex]; // = Product X
dataGrid.SelectedItem = item;
DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
if (row == null)
{
/* bring the data item (Product object) into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(item);
row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
}
//TODO: Retrieve and focus a DataGridCell object
if (row != null)
{
DataGridCell cell = GetCell(dataGrid, row, 0);
if (cell != null)
cell.Focus();
}
}
public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
if (rowContainer != null)
{
DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter == null)
{
/* if the row has been virtualized away, call its ApplyTemplate() method
* to build its visual tree in order for the DataGridCellsPresenter
* and the DataGridCells to be created */
rowContainer.ApplyTemplate();
presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
}
if (presenter != null)
{
DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
if (cell == null)
{
/* bring the column into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return cell;
}
}
return null;
}
public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
foreach (childItem child in FindVisualChildren<childItem>(obj))
{
return child;
}
return null;
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}

Coding multiple viewmodels in WPF XAML MVVM

for my project, i am trying to create a signal channel generator which connects to a toolset and pushes signals into it.
the issue i have is that i have been given the project in a form where the code for the textboxes are in the codebehind file, and i would like them to be in the xaml.
i have a variable which controls the number of channels (viewmodels) which can be changed. which is able to create multiple instances of the same viewmodel on the window. this allows the ability to select different targets inside the tool whcih it is communicating with and be able to pump signals to each target.
here is the code currently in the XAML:
<Window x:Class="SigGeneratorMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SigGeneratorMVVM"
Title="Signal Generator" Height="370" Width="734" >
<StackPanel Name="MyWindow">
<!--<TextBox Height="23" HorizontalAlignment="Left" Margin="91,20,0,0" Name="CurrentValDisplay" VerticalAlignment="Top" Width="120" />-->
</StackPanel>
</Window>
here is the code for the mainwindow.cs
public partial class MainWindow : Window
{
List<ViewModel> gViewModels;
int gNumChannels = 1;
private System.Threading.Timer mViewUpdateTimer;
private TimerCallback mViewTimerCallback;
private UtilityParticipant mParticipant;
public MainWindow()
{
InitializeComponent();
// Connect as UtilityParticipant
ConnectMesh();
gViewModels = new List<ViewModel>();
for (int i = 0; i < gNumChannels; i++)
{
gViewModels.Add(new ViewModel(mParticipant));
TextBlock CurrentValueText = new TextBlock();
CurrentValueText.Text = "Current Value:";
CurrentValueText.Margin = new Thickness(5);
TextBox CurrentValueBox = new TextBox();
CurrentValueBox.Width = 120;
CurrentValueBox.Name = "CurrentValDisplay" + i.ToString();
CurrentValueBox.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
CurrentValueBox.Margin = new Thickness(10);
CurrentValueBox.SetBinding(TextBox.TextProperty, "CurrentValue");
//CurrentValDisplay.Name = "CurrentValDisplay" + i.ToString();
//CurrentValDisplay.SetBinding(TextBox.TextProperty, "CurrentValue");
TextBlock CurrentFrequencyText = new TextBlock();
CurrentFrequencyText.Text = "Frequency:";
CurrentFrequencyText.Margin = new Thickness(5);
TextBox CurrentFrequencyBox = new TextBox();
CurrentFrequencyBox.Width = 120;
CurrentFrequencyBox.Name = "CurrentFrequencyDisplay" + i.ToString();
CurrentFrequencyBox.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
CurrentFrequencyBox.Margin = new Thickness(10);
CurrentFrequencyBox.SetBinding(TextBox.TextProperty, "Frequency");
Slider FrequencySlider = new Slider();
FrequencySlider.Width = 200;
FrequencySlider.Name = "FrequencySet" + i.ToString();
FrequencySlider.Value= 10;
FrequencySlider.Maximum = 10;
FrequencySlider.Minimum = 0.1;
FrequencySlider.SetBinding(Slider.ValueProperty, "Frequency");
//Create a new stackpanel
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Vertical;
//Set DataContext of the StackPanel
sp.DataContext = gViewModels[i];
//Add controls created above to the StackPanel
sp.Children.Add(CurrentValueText);
sp.Children.Add(CurrentValueBox);
sp.Children.Add(CurrentFrequencyText);
sp.Children.Add(CurrentFrequencyBox);
sp.Children.Add(FrequencySlider);
//Add the StackPanel to the window
MyWindow.Children.Add(sp);
}
mViewTimerCallback = this.UpdateView;
mViewUpdateTimer = new System.Threading.Timer(mViewTimerCallback, null, 100, 20);
}
Update: I already have a ViewModel which has get set methods for each property (CurrentValue and Frequency for now), would it be sufficient to bind the DataTemplate and ItemsControl to that instead of creating a new model class?
private SigGenChannel mSigGenChannel;
//Constructor
public ViewModel(UtilityParticipant aParticipant)
{
mSigGenChannel = new SigGenChannel(aParticipant);
}
public string CurrentValue
{
get
{
return mSigGenChannel.CurrentValue.ToString();
}
set
{
mSigGenChannel.CurrentValue = double.Parse(value);
RaisePropertyChanged("CurrentValue");
}
}
public double Frequency
{
get
{
return mSigGenChannel.Frequency;
}
set
{
mSigGenChannel.Frequency = value;
RaisePropertyChanged("Frequency");
}
}
public double Amplitude
{
get
{
return mSigGenChannel.Amplitude;
}
set
{
mSigGenChannel.Amplitude = value;
RaisePropertyChanged("Amplitude");
}
}
public void RefreshValue()
{
//A bit of a cheat, but we provide a means to poke the Viewmodel
//And raise a property change event
RaisePropertyChanged("CurrentValue");
}
also this is the SigChannel model:
class SigGenChannel
{
#region Private members
private UtilityParticipant mParticipant;
private double mCurrentValue;
private double mFrequency;
private double mAmplitude;
private double mTarget;
private double mOffset;
private double mCurrentStepTime;
private DateTime mStartTime;
private System.Threading.Timer mTimer;
private TimerCallback mTCallback;
private int mUpdateInterval = 10;
#endregion
#region Public members
public double CurrentValue
{
get
{
return mCurrentValue;
}
set
{
mCurrentValue = value;
}
}
public double Frequency
{
get
{
return mFrequency;
}
set
{
mFrequency = value;
}
}
public double Amplitude
{
get
{
return mAmplitude;
}
set
{
mAmplitude = value;
}
}
public double Target
{
get
{
return mTarget;
}
set
{
mTarget = value;
}
}
#endregion
//Constructor
public SigGenChannel(UtilityParticipant aParticipant)
{
mParticipant = aParticipant;
mCurrentValue = 10;
mFrequency = 200;
mAmplitude = 100;
mOffset = 0;
mCurrentStepTime = 0;
mStartTime = DateTime.Now;
mTCallback = this.Update;
mTimer = new System.Threading.Timer(mTCallback, null, 500, mUpdateInterval);
//Array enumData = Enum.GetNames;
//RefreshItems();
//Temp Code....!
Collection lCollection = mParticipant.GetCollection("DefaultNodeName.NodeStats");
lCollection.Publish();
}
private void Update(object StateInfo)
{
TimeSpan span = DateTime.Now - mStartTime;
mCurrentStepTime = span.TotalMilliseconds / (double)1000;
mCurrentValue = (Math.Sin(mCurrentStepTime * (mFrequency * 2 * Math.PI)) * mAmplitude / 2) + mOffset;
//Temp Code...!
Collection lCollection = mParticipant.GetCollection("DefaultNodeName.NodeStats");
Parameter lParameter = lCollection.GetParameter("CPUPercent");
lParameter.SetValue(mCurrentValue);
lCollection.Send();
The current code is written in a way that does not follow the WPF suggested practices, and swimming against the current makes things a lot harder than then should be.
What the code should be doing is:
Create a (view)model class for a channel
For example:
class ChannelModel
{
public int Value { get; set; }
public int Frequency { get; set; }
}
Use an ItemsControl instead of a StackPanel
The WPF way of doing things like this is to bind controls to collections, so replace the StackPanel with an ItemsControl.
Bind the ItemsControl to an ObservableCollection of the models
Your main viewmodel should expose an ObservableCollection<ChannelModel> property and the control should bind to that directly:
<ItemsControl ItemsSource="{Binding CollectionOfChannelModels}"/>
This ensures that the control is automatically updated with any changes made to your collection without your needing to do anything else.
Use a DataTemplate to specify how each model should render
So far we 've gotten the control to stay in sync with your channel collection, but we also need to tell it how each item (channel model) should be displayed. To do this, add a DataTemplate to the Resources collection of the ItemsControl:
<ItemsControl.Resources>
<DataTemplate DataType={x:Type local:ChannelModel}>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Value" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
Generally the idea is to create a DataTemplate for a specific type, in your case it is for ViewModel.
Create a DataTemplate for your ViewModel, for instance:
<DataTemplate DataType={x:Type local:ViewModel}>
<TextBox Text="{Binding ViewModelTextProperty}" />
</DataTemplate>
And also in your XAML you must bind your list of ViewModels
<ItemsControl ItemsSource="{Binding myListOfViewModels}"/>

Silverlight ComboBox force reselect SelectedItem

I've got a list of items bound to a ComboBox. When a user selects an item, I'd like to cancel the selection and select a different item instead. This must happen from within the setter of the property that the SelectedItem is bound to. I'm using Silverlight 3.
My data model for each item in the ComboBox:
public class DataItem
{
public int Id { get; set; }
public string Name { get; set; }
}
Object that is set to the DataContext:
public class DataContainer : INotifyPropertyChanged
{
public DataContainer()
{
itemList = new List<DataItem>();
itemList.Add(new DataItem() { Id = 1, Name = "First" });
itemList.Add(new DataItem() { Id = 2, Name = "Second" });
itemList.Add(new DataItem() { Id = 3, Name = "Third" });
}
public event PropertyChangedEventHandler PropertyChanged;
private DataItem selectedItem;
public DataItem SelectedItem
{
get { return selectedItem; }
set
{
if (value != null && value.Id == 2)
value = itemList[0];
selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private List<DataItem> itemList;
public List<DataItem> ItemList
{
get { return itemList; }
set { itemList = value; NotifyPropertyChanged("DataList"); }
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Relevant bits of xaml:
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox" DisplayMemberPath="Name" Width="100" ItemsSource="{Binding ItemList}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
<Button Content="Set to First" Width="100" Click="Button_Click"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Selected item: "/>
<TextBlock Text="{Binding SelectedItem.Id}"/>
<TextBlock Text=" - "/>
<TextBlock Text="{Binding SelectedItem.Name}"/>
</StackPanel>
</StackPanel>
It looks like my code to select the first item when the user selects the second item is working. The selected item is in fact set to "First" while the ComboBox is still displaying "Second" as if it was selected.
Is there any way to force the ComboBox to redraw or to reconsider what it should visually mark as selected?
I do this from the above mentioned Button_Click method and it works:
private void Button_Click(object sender, RoutedEventArgs e)
{
var c = DataContext as DataContainer;
if (c != null)
{
c.SelectedItem = null;
c.SelectedItem = c.ItemList[0];
}
}
But setting to null and then the value I want doesn't work if I do it from within the setter like I need to.
I may have found a solution for you. I was able to get what I think you're asking for working by doing the following:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
if (value != null && value.Id == 2)
{
value = itemList[0];
UpdateUI(); // Call this to force the UI to update.
}
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
private void UpdateUI()
{
ThreadPool.QueueUserWorkItem(
o =>
{
Thread.Sleep(1);
Deployment.Current.Dispatcher.BeginInvoke(()=>
{
_selectedItem = null;
NotifyPropertyChanged("SelectedItem");
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem");
});
});
}
I wish I could explain to you why this is working, but I can only guess. Basically, its exiting the UI thread, and then re-entering a moment later via the Dispatcher.BeginInvoke() call. This appears to give the ComboBox control time to update itself from the user interaction, and then respond to the Dispatcher execution.
One problem I've found is that Silverlight seems to go a little wonky after multiple executions of the threading code. Increasing the Thread.Sleep time seems to help. I think this solution will work for the majority of situations and won't be an issue.
You don't have to queue a Thread with 1 second waiting as grimus suggested. this should also work for you:
public DataItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
if (value != null && value.Id == 2)
{
Diployment.Current.Dispatcher.BeginInvoke(() => {
_selectedItem = itemList[0];
NotifyPropertyChanged("SelectedItem"); });
}
}
}

Resources