I know question similar to this has asked several times in SO. But non of them address my issue and have some difficulty with understanding those answers. This is my situation; I have a ItemsControl I have used ItemTemplate and bound some data.
<Window.Resources>
<DataTemplate x:Key="AdditionalFieldTemlate">
<Grid>
<TextBlock Text="{Binding InfoName}"/>
<TextBox Text="{Binding InfoValue,Mode=TwoWay}" Name="CustomValue"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding AdditionalInformation}" x:Name="additionalInfo" ItemTemplate="{DynamicResource AdditionalFieldTemlate}"/>
</Grid>
I need to set TextBox text to empty(all the textboxes text inside datatemplate) once click on a Button. Don't know how to access these textboxes. Please help me.
You don't normally access the TextBoxes (the look) ....you access the data that is being bound to.
So you can just alter the "data" in your collection as follows:
foreach (var item in AdditionalInformation)
{
item.InfoValue = "";
}
The "TextBoxes" will then be emptied.
Make sure you have implemented INotifyPropertyChanged on the class being used by AdditionalInformation....so that when the InfoValue property is altered it raises a notification.
The text in the textboxes is databound to the InfoValue property of your class. Implement the class and proprty like this:
class InfoClass: INotifyPropertyChanged
{
private string _infoValue;
...
public string InfoValue
{
get { return _infoValue; }
set
{
_infoValue = value;
OnNotifyPropertyChanged("InfoValue")
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Then do what colinsmith suggests in your button click handler (or command if you went with the MVVM approach). The binding will be notified to the change and the view will be updated.
Related
I have been going through posts for 3 hours now with no resolution. I am new to WPF and created the ComboBox below:
Unfortunately I cannot disable the highlighting of the selected item. Does anyone have a viable solution?
Code:
<StackPanel Grid.Column="1"
Margin="800,0,0,0"
Width="135"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<ComboBox Name="LangComboBox"
IsEditable="True"
IsReadOnly="True"
Text="Select Language">
<ComboBoxItem>English</ComboBoxItem>
<ComboBoxItem>Spanish</ComboBoxItem>
<ComboBoxItem>Both</ComboBoxItem>
</ComboBox>
</StackPanel>
I would like to clarify first of all that mine wants to be constructive answer and want to try to spread the culture of good programming.
We all have always to learn about programming, me too!
If you do not know a topic, it is good practice to study perhaps starting from a good book or from the official documentation of the platform.
That said let's move on to some possible approaches to your problem.
First of all, the fact that the selection in the combobox is that way is due to the basic template of the combobox that I invite you to view: https://msdn.microsoft.com/library/ms752094(v=vs.85).aspx )
What you are looking for is a different behavior of the combobox:
Allow display of a default value
Once an element is selected, the text inside it is not underlined
A first approach could be based on the ComboBox template: the combobox is constructed in such a way that, if it is editable, its template
contains a textbox called PART_EditableTextBox
by acting on the textbox, for example by making it disabled, you can get the result you want.
And this can be implemented in different ways:
Inserting a code-behind event handler that disables the textbox when the combobox is loaded
With an Attached behavior that allows you to add custom behaviors to the controls (https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF)
Write a custom control that maybe insert a watermark type part to your combobox
Now consider the first approach that is the fastest to implement so the code could be the following:
<ComboBox Name="LangComboBox" IsEditable="True" IsReadOnly="True"
Loaded="LangComboBox_Loaded"
Text="Select language">
<ComboBoxItem Content="English"/>
<ComboBoxItem Content="Spanish"/>
<ComboBoxItem Content="Both"/>
</ComboBox>
In the code-behind:
private void LangComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox ctrl = (ComboBox)sender;
TextBox Editable_tb = (TextBox)ctrl.Template.FindName("PART_EditableTextBox", ctrl);
if (Editable_tb != null)
{
// Disable the textbox
Editable_tb.IsEnabled = false;
}
}
This approach, however, has drawbacks, among which the fact that if the user wants to deselect / reset the value of the combo can not do it.
So you could follow another path using the MVVM pattern.
Coming from the world of web programming you should know the MVC pattern, in WPF the most common pattern is MVVM or Model - View - ViewModel
between the two patterns there are different things in common and I invite you to take a look at them: Mvvm Pattern.
You could create a class with the model that will be hosted in the combo for example:
public class Language
{
public int Id { get; set; }
public string Description { get; set; }
public Language(int id, string desc)
{
this.Id = id;
this.Description = desc;
}
}
public class YourDataContext : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<Language> _Languages;
public List<Language> Languages
{
get
{
return _Languages;
}
set
{
_Languages = value;
OnPropertyChanged("Languages");
}
}
private Language _selectedLanguage;
public Language SelectedLanguage
{
get
{
return _selectedLanguage;
}
set
{
_selectedLanguage = value;
OnPropertyChanged("SelectedLanguage");
}
}
public YourDataContext()
{
// Initialization of languages
Languages = new List<Language>();
Languages.Add(new Language(0, "None - Select a Language"));
Languages.Add(new Language(1, "English"));
Languages.Add(new Language(2, "Spanish"));
Languages.Add(new Language(3, "Both"));
SelectedLanguage = Languages.First();
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
// some other properties and commands
}
// Your Window class
public MainWindow()
{
InitializeComponent();
var dc = new YourDataContext();
DataContext = dc;
}
<ComboBox ItemsSource="{Binding Languages}"
DisplayMemberPath="Description"
SelectedItem="{Binding SelectedLanguage}"/>
Note that now the combobox is no longer editable and it is possible to reset the selection.
You can manage the selection using the model:
if(dc.SelectedLanguage.Id == 0)
{
//No language selected
}
There are a lot of different ways to achieve what you want, i hope this gave you some good point to start from.
Good programming to everyone.
New to the WPF + XAML + MVVM stack so I'm sure I'm doing something basic here, but Googling hasn't helped me figure it out. I think A second set of eyes may help.
The Setup
I have a list of Objects called FilesToAdd
I have a DataGrid bound to this list
I have a drag and drop event that fires handling code
I've confirmed this works via Console.WriteLine() output.
The Goal
When an item is added to the list, I'd like the datagrid to be updated with the appropriate information that has just been added to the list.
The Problem
The list seems to be updated, but the datagrid never is.
The Code
Showing only the relevant parts.
UploaderViewModel Class
private ObservableCollection<IAddableFile> _filesToAdd;
public event PropertyChangedEventHandler PropertyChanged;
public UploaderViewModel()
{
_filesToAdd = new ObservableCollection<IAddableFile>();
}
protected virtual void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public ObservableCollection<IAddableFile> FilesToAdd
{
get { return _filesToAdd; }
set
{
if (value != _filesToAdd)
{
_filesToAdd = value;
OnPropertyChanged("FilesToAdd");
OnPropertyChanged("FilesAreQueued");
}
}
}
public bool FilesAreQueued
{
get { return (FilesToAdd.Count > 0); }
}
public void AFileHasBeenAdded(string filepath)
{
var message = String.Format("File dropped: {0}", filepath);
Console.WriteLine(message);
var newFileInfo = new FileInfo(filepath);
if (newFileInfo.Exists && newFileInfo.Length > 0 && (!FileIsADirectory(newFileInfo))) // only add the file to the ViewModel if it's
{
FilesToAdd.Add(new FileSystemFile(newFileInfo)); //Creating our own type becaause we do additional things with it
Console.WriteLine(String.Format("File added to list: {0}", newFileInfo.FullName));
}
}
XAML Binding
<DataGrid ItemsSource="{Binding FilesToAdd}" Height="100" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="100" AutoGenerateColumns="False" Visibility="{Binding FilesAreQueued, Converter={StaticResource BoolToVisConverter}}">
<DataGrid.Columns>
<DataGridTextColumn Header="File Name" Binding="{Binding FileName}"/>
<DataGridTextColumn Header="Size" Binding="{Binding FileSizeInText}"/>
</DataGrid.Columns>
</DataGrid>
What am I missing? I've been looking at the pattern and I know it has to be something simple I'm not seeing due to staring at a screen for too long. :)
Edit: I suspect the DataGrid updates just fine but you can't see it because the FilesAreQueued property is lying.
You would need something like
FilesToAdd.CollectionChanged += (s,e) =>
OnPropertyChanged("FilesAreQueued");
As you only want to do that once (if at all, can bind to FilesToAdd.Count directly), you really should opt for a readonly collection field.
Looks fine if the DataContext of the view is actually that view-model.
Another issue could be that the class is not implementing INotifyPropertyChanged (you can have the event without actually implementing it using class : interface), this would only apply if you overwrite the FilesToAdd property with a new instance. (In general i expose collections as get-only with a readonly field.)
Might want to check for binding errors (don't think you get any for bindings to a null DataContext though).
(Also i would recommend making the OnPropertyChanged thread-safe, i.e. var handler = <event>; if (handler != null) handler();)
In my WPF application, I am developing a fairly straightforward page that allows either creating a new object or choosing one from a combo box, then editing the object.
One of the parts of the object that is editable is a related database table in a one-to-many relationship, so for that piece I used a DataGrid. The DataGrid itself has a data-bound ComboBox column, as you can see here:
<DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True"
CanUserAddRows="False" CanUserDeleteRows="True"
ItemsSource="{Binding Path=No.Lower_Assy}"
DataGridCell.Selected="dgAssy_GotFocus">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Number & Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.ComboSource, RelativeSource={RelativeSource AncestorType=Page}}"
SelectedValuePath="bwk_No"
SelectedValue="{Binding Path=fwf_Higher_N, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Number}"/>
<TextBlock Text="{Binding Path=Type}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- other text columns omitted -->
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Delete" Click="btnDeleteHigherAssy_Click" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Code behind:
private void dgAssy_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() == typeof(DataGridCell))
{
// Starts the edit on the row
DataGrid grd = (DataGrid)sender;
grd.BeginEdit(e);
}
}
And for the save button:
private void btnSave_Click(object sender, RoutedEventArgs e)
{
if (CanUserEdit())
{
if (string.IsNullOrWhiteSpace(model.Data.Error))
{
repo.Save(model.Data);
StatusText = STATUS_SAVED;
model.CanSave = false;
// This is the data source for the main combo box on the page
model.ComboSource = repo.GetData();
// Set the combo box's selected item, in case this is a new object.
// cboNo is the main combo box on the page which allows selecting
// an object to edit
// Apparently setting SelectedItem directly doesn't work on a databound combo box
int index = model.ComboSource.ToList().FindIndex(x => x.bwk_No == model.Data.bwk_No);
cboNo.SelectedIndex = index;
}
else
{
MessageBox.Show("Invalid data:\n" + model.Data.Error, "Cannot save");
}
}
}
The problem
When I choose an item from the combo box in the data grid, it seems to work until I click on the save button. Then two things happen:
The combo box's selected item is set to null, blanking out the combo box.
As a result of (1), the save button is re-enabled because the data has changed. (The save button is bound to model.CanSave, which as you can see is set to false in the button handler; it is set to true by a property change event handler if there are no data errors.)
Why is it being reset? I've followed the code flow closely and can see the property change event for the combo box's backing field (fwf_Higher_N) being handled, and it appears to somehow come from the line model.ComboSource = repo.GetData();, but the stack only shows [external code] and I don't see why that line would modify an existing object.
The model class
// Names have been changed to protect the innocent
private class MyDataViewModel : INotifyPropertyChanged
{
private DbData _Data;
public DbData Data
{
get { return _Data; }
set
{
_Data = value;
OnPropertyChanged("Data");
}
}
private IQueryable<MyComboModel> _ComboSource;
public IQueryable<MyComboModel> ComboSource {
get { return _ComboSource; }
set
{
_ComboSource = value;
OnPropertyChanged("ComboSource");
}
}
private bool _CanSave;
public bool CanSave
{
get { return _CanSave; }
set
{
_CanSave = value;
OnPropertyChanged("CanSave");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Your description of what is going on and your markup doesn't quite match. I'm going to make some assumptions, such as that Page.DataContext is an instance of MyDataViewModel.
I'm sorry to say it, but a SSCCE would do wonders here. I strongly suggest when anyone gets into situations where they are elbow deep in code they don't quite understand that they break out what they are attempting to do and create a minimal prototype that either exhibits the same behavior, or that helps you learn what's going wrong. I've made 500+ prototypes in the past five years.
As for this situation, you refer to a ComboBox named cboNo in btnSave_Click, but I don't see that in the xaml. This ComboBox's ItemSource appears to be bound to MyDataViewModel.ComboSource.
In addition, all ComboBoxes in the DataGrid also appear to be bound to the model's ComboSource. And, in the button handler event, you change what is in the property:
// This is the data source for the main combo box on the page
model.ComboSource = repo.GetData();
This fires PropertyChanged, and every ComboBox bound to this property will be updated. That means not only cboNo but also every ComboBox in the DataGrid.
It is expected behavior that, when ComboBox.ItemsSource changes, if ComboBox.SelectedItem is not contained within the items source, that SelectedItem is nulled out.
I just spun up a prototype (501+) and it appears that if the IEnumerable that the ComboBox is bound to changes, but the elements in the IEnumerable do not, then SelectedItem is not nulled out.
var temp = combo.ItemsSource.OfType<object>().ToArray();
combo.ItemsSource = temp;
So, within the btnSave_Click event handler, you change this ItemsSource, which probably does not have the same instances that are already in the combo, thus nulling out SelectedItem for all ComboBoxes bound to this property, and then only update cboNo's SelectedIndex.
Now, as for what to do about it...
Well, not sure. From the rest of your code, it appears you need to do some more codebehind work to make sure only the necessary ComboBoxes have their sources updated...
This is a running question that I have updated to hopefully be a little more clear.
In short what I am trying to accomplish is pass a property from a listbox selected item to the viewmodel so that this property can be used within a new query. In the code below the Listbox inherits databinding from the parent object. The listbox contains data templates (user controls) used to render out detailed results.
The issue I am having is that within the user control I have an expander which when clicked calls a command from the ViewModel. From what I can see the Listbox object is loosing it's data context so in order for the command to be called when the expander is expanded I have to explicitly set the datacontext of the expander. Doing this seems to instantiate a new view model which resets my bound property (SelectedItemsID) to null.
Is there a way to pass the selected item from the view to the viewmodel and prevent the value from being reset to null when a button calls a command from within the templated listbox item?
I realize that both Prism and MVVMLite have workarounds for this but I am not familiar with either framework so I don't know the level of complexity in cutting either of these into my project.
Can this be accomplished outside of Prism or MVVMLite?
original post follows:
Within my project I have a listbox usercontrol which contains a custom data template.
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding SearchResults[0].Results,
Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionChanged="ResultListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
<!-- CFS Template -->
<dts:TypeTemplateSelector.CFSTemplate>
<DataTemplate>
<qr:srchCFS />
</DataTemplate>
</dts:TypeTemplateSelector.CFSTemplate>
<!-- Person Template -->
<dts:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:srchPerson />
</DataTemplate>
</dts:TypeTemplateSelector.PersonTemplate>
<!-- removed for brevity -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectionChanged calls the following method from the code behind
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);//binds to VM
}
Within the ViewModel I have the following property
public string SelectedItemID
{
get
{
return this._SelectedItemID;
}
set
{
if (this._SelectedItemID == value)
return;
this._SelectedItemID = value;
}
}
the listbox template contains a custom layout with an expander control. The expander control is used to display more details related to the selected item. These details (collection) are created by making a new call to my proxy. To do this with an expander control I used the Expressions InvokeCommandAction
<toolkit:Expander Height="auto"
Margin="0,0,-2,0"
Foreground="#FFFFC21C"
Header="View Details"
IsExpanded="False"
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
Style="{StaticResource DetailExpander}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding GetCfsResultCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Within the ViewModel the delegate command GetCFSResultCommandExecute which is called is fairly straight forward
private void GetCfsResultCommandExecute(object parameter)
{
long IdResult;
if (long.TryParse(SelectedItemID, out IdResult))
{
this.CallForServiceResults = this._DataModel.GetCFSResults(IdResult);}
The issue I am experiencing is when selecting a listbox Item the selectionchanged event fires and the property SelectedItemID is updated with the correct id from the selected item. When I click on the expander the Command is fired but the property SelectedItemID is set to null. I have traced this with Silverlight-Spy and the events are consistent with what you would expect when the expander is clicked the listbox item loses focus, the expander (toggle) gets focus and there is a LeftMouseDownEvent but I cannot see anything happening that explains why the property is being set to null. I added the same code used in the selection changed event to a LostFocus event on the listboxt item and still received the same result.
I'd appreciate any help with understanding why the public property SelectedItemID is being set to null when the expander button which is part of the listbox control is being set to null. And of course I would REALLY appreciate any help in learning how prevent the property from being set to null and retaining the bound ID.
Update
I have attempted to remove the datacontext reference from the Expander as this was suggested to be the issue. From what I have since this is a data template item it "steps" out of the visual tree and looses reference to the datacontext of the control which is inherited from the parent object. If I attempt to set the datacontext in code for the control all bindings to properties are lost.
My next attempt was to set the datacontext for the expander control within the constructor as
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
This approach does not seem to work as InvokeCommandAction is never fired. This command only seems to trigger if data context is set on the expander.
thanks in advance
With this line you create a new SearchViewModelDataSource using its default constructor.
DataContext="{Binding Source={StaticResource SearchViewModelDataSource}}"
I guess this is why you find null because this is the default value for reference type.
You can resolve the issue by setting DataContext to the same instance used to the main controll (you can do it by code after all components are initialized).
Hope this help!
Edit
I don't think that binding may be lost after setting datacontext from code. I do it every time I need to share something between two or more model.
In relation to the code you've written :
private SearchViewModel _ViewModel;
public srchCFS()
{
InitializeComponent();
this.cfsExpander.DataContext = this._ViewModel;
}
Instead of using this.cfsExpander you can try to use the FindName method. Maybe this will return you the correct instance.
object item = this.FindName("expander_name");
if ((item!=null)&&(item is Expander))
{
Expander exp = item as Expander;
exp.DataContext = this._ViewModel;
}
Try if its work for you.
Of course, this._ViewModel has to expose a property of type ICommand named GetCfsResultCommand but I think this has been already done.
While this was a hacky approach I found an intermediate solution to get the listbox item value to the view model. I ended up using the selection changed event and passing the value directly to a public property wihtin my view model. Not the best approach but it resolved the issue short term
private void ResultListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (((ListBox)sender).SelectedItem != null)
_ViewModel.SelectedItemID = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
MySelectedValue = (((ListBox)sender).SelectedItem as QueryResult).ID.ToString();
this.NotifyPropertyChanged(_ViewModel.SelectedItemID);
}
For this to fire I did have to also setup a property changed handler within the view to push the change to the VM. You can disregard the MySelectedValue line as it is secondary code I have in place for testing.
For those intereted the generic property changed handler
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I have a datagrid that is multi-select enabled. I need to change the selection in the viewmodel. However, the SelectedItems property is read only and can't be directly bound to a property in the viewmodel. So how do I signal to the view that the selection has changed?
Andy is correct. DataGridRow.IsSelected is a Dependency Property that can be databound to control selection from the ViewModel. The following sample code demonstrates this:
<Window x:Class="DataGridMultiSelectSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<StackPanel>
<tk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}" EnableRowVirtualization="False">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Value" Binding="{Binding Value}" />
</tk:DataGrid.Columns>
<tk:DataGrid.RowStyle>
<Style TargetType="tk:DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</tk:DataGrid.RowStyle>
</tk:DataGrid>
<Button Content="Select Even" Click="Even_Click" />
<Button Content="Select Odd" Click="Odd_Click" />
</StackPanel>
</Window>
using System.ComponentModel;
using System.Windows;
namespace DataGridMultiSelectSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new[]
{
new MyViewModel {Value = "Able"},
new MyViewModel {Value = "Baker"},
new MyViewModel {Value = "Charlie"},
new MyViewModel {Value = "Dog"},
new MyViewModel {Value = "Fox"},
};
}
private void Even_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[]) DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i%2 == 0;
}
private void Odd_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[])DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i % 2 == 1;
}
}
public class MyViewModel : INotifyPropertyChanged
{
public string Value { get; set; }
private bool mIsSelected;
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (mIsSelected == value) return;
mIsSelected = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Be sure to set EnableRowVirtualisation="False" on the DataGrid element, else there's a risk that the IsSelected bindings fall out of kilter.
I haven't worked with the DataGrid much, but one technique that works for the ListView is to bind to the IsSelected property of the individual ListViewItem. Just set this to true for each object in your list, and then it will get selected.
Maybe the object that represents a row in the DataGrid also has an IsSelected property, and can be used in this way as well?
Guys, thanks for the help. My problem was solved. I think the problem is pretty common for new WPF developers, so I will restate my problem and as well as the solution in more details here just in case someone else runs into the same kind of problems.
The problem: I have a multi-select enabled datagrid of audio files. The grid has multiple column headers. The user can multi-select several row. When he clicks the Play button, the audio files will be played in the order of one the columns headers (say column A). When playback starts, the multi-select is cleared and only the currently playing file is highlighted. When playback is finished for all files, the multi-selection will be re-displayed. The playback is done in the viewmodel. As you can see, there are two problems here: 1) how to select the currently playing file from the viewmodel, and 2) how to signal to the view from the viewmodel that playback is finished and re-display the multi-selection.
The solution: To solve the first problem, I created a property in the viewmodel that is bound to the view's SelectedIndex property to select the currently playing file. To solve the second problem, I created a boolean property in the view model to indicate playback is finished. In the view's code behind, I subscribed the the boolean property's PropertyChanged event. In the event handler, the view's SelectedItems property is re-created from the saved multi-selection (the contents of SelectedItems was saved into a list and SelectedItems was cleared when playback started). At first, I had trouble re-creating SelectedItems. It turned out the problem was due to the fact that re-creation was initiated through a second thread. WPF does not allow that. The solution to this is to use the Dispatcher.Invoke() to let the main thread do the work. This may be a very simple problem for experienced developers, but for newbies, it's a small challenge. Anyway, a lot of help from different people.
Just use SelectedItems on any MultiSelector derived class , and use methods Add, Remove, Clear on IList it returns .