I have a datagridview populated with items and I am using a SelectionChanged event to populate textboxes from that data when selected.
If I make a selection, everything works. If I click elsewhere in the App and then come back to click the SelectionChanged event again on the same item - it doesn't work.
According to MSDN:
"This event occurs whenever there is a change to a selection."
MSDN SelectionChangedEvent
So it appears that despite clicking elsewhere, resetting the Textboxes - the selected item is not changing as the SelectionChanged event no longer triggers - click on another item and it works, click back again and it works - but click on it, reset textboxes, click it again - nothing happens, this includes clicking in the datagridview itself in a blank area.
XAML:
<DataGrid x:Name="TimeView" Grid.Row="1" Grid.Column="3"
Grid.ColumnSpan="3" Grid.RowSpan="4" Margin="10 50 10 10"
CanUserAddRows="False" Visibility="{Binding StartTiming}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cal:ActionMessage MethodName="SelectedTimeChangeEvent">
<cal:Parameter Value="$eventArgs" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
ViewModel
public void SelectedTimeChangeEvent(SelectionChangedEventArgs e)
{
foreach (TimeData addedRow in e.AddedItems)
{
TbID = addedRow.ID;
TbDate = addedRow.Date;
TbStartTime = addedRow.StartTime;
TbDescription = addedRow.Description;
}
}
Since I am using MVVM and Caliburn, TimeView is connected to an ICollection, which is in turn connected to an ObservableCollection:
private ObservableCollection<TimeData>? _timeCollection;
public ObservableCollection<TimeData>? TimeCollection
{
get { return _timeCollection; }
set
{
_timeCollection = value;
NotifyOfPropertyChange(() => TimeCollection);
}
}
private ICollectionView? _timeView;
public ICollectionView? TimeView
{
get { return _timeView; }
set
{
_timeView = value;
NotifyOfPropertyChange(() => TimeView);
}
}
There is a work around, which is the following after populating the Textboxes:
TimeView = null;
TimeView = CollectionViewSource.GetDefaultView(TimeCollection);
This works, but I thought that there might be a "deselect" option that would be better than repopulating every time a selection is made, one of my Datagrids contains 15,000 items, and it is still instant, but seems overkill to populate it every time a selection is made.
i would recommend bindings, they automaticly reset when nothing is selected
<DockPanel>
<StackPanel DataContext="{Binding SelectedTime}" DockPanel.Dock="Left">
<TextBlock Text="{Binding ID}"/>
<TextBlock Text="{Binding Date}"/>
<TextBlock Text="{Binding StartTime}"/>
<TextBlock Text="{Binding Description}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding TimeView}" SelectedItem="{Binding SelectedTime}">
...
</DataGrid>
</DockPanel>
public TimeData SelectedTime
{
get { return _selectedTime; }
set
{
_selectedTime = value;
NotifyOfPropertyChange(() => SelectedTime);
}
}
also there is this neet feature
protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
OnPropertyChanged(propertyName);
}
so you can write
set { SetValue(ref _selectedTime, value) }
Related
I have a listview in WPF in an MVVM/PRISM app which may contain 1-to-many elements. When the listview contains only 1 element, and I select it, I cannot subsequently reselect it even though I set the SelectedIndedx value to -1. Worse, if I make the app update the listview with a different single element, I can't select that one either. The only way I can achieve selection of an item when it is the only item in the listview is to make the app display multiple items and select something other than the first. Then, when I make the app display a listview containing a single item, I can select it again - but only once.
In those cases where I cannot select the single item in the listview, the servicing routine never fires.
I tried implementing a XAML suggestion I found here using "Listview.Container.Style" and the IsSelected property, but that did not work.
My listview is fairly straightforward:
<ListView Name="lstEditInstance"
Grid.Row="5"
ItemsSource="{Binding Path=InstanceList,Mode=TwoWay}"
Width="488"
FontFamily="Arial" FontSize="11"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Margin="10,96,0,28"
SelectedIndex="{Binding Path=InstanceSelectedIndex}">
</ListView>
The servicing routine is:
private void OnInstanceSelectedIndexChanged()
{
// Handle case where user hits Enter without making a selection:
if (_instanceIndex == -1) return;
// Get the instance record for the row the user clicked on as a
// ResourceInstance class named "InstanceRecord".
InstanceRecord = _instanceList[_instanceIndex];
_instanceNumber = InstanceRecord.Instance;
FormInstName = InstanceRecord.InstName;
FormInstEnabled = InstanceRecord.Enabled;
FormInstState = InstanceRecord.InitialState;
FormInstIPAddress = InstanceRecord.IPAddress;
FormInstPort = InstanceRecord.Port.ToString();
FormInstSelectedURL = InstanceRecord.UrlHandler;
} // End of "OnResourceSelectedIndexChanged" method.
"InstanceList" is an observable collection.
I'd appreciate some suggestions. Thanks in advance for any help.
In a MVVM scenario, I'd use a ViewModel that contains the selected item instead:
class MyViewModel {
private IList<Item> instanceList= new List<Item>();
public IList<Item> List
{
get {return list; }
set {
list = value;
RaisePropertyChanged(() => List);
}
}
private Item selectedItem;
public Item SelectedItem {
get {return selectedItem;}
set {
selectedItem = value;
RaisePropertyChanged(() => SelectedItem);
}
}}
And the XAML:
<ListView Name="lstEditInstance"
Grid.Row="5"
ItemsSource="{Binding Path=InstanceList}"
Width="488"
FontFamily="Arial" FontSize="11"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Margin="10,96,0,28"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}}">
Notice that observableCollection is not required unless you have to modify the list items, in the same way the binding should be the default one for the list.
The SelectedItem / SelectedIndex should be TwoWay or Onewaytosource, the latter if you think you don't need to change the selectedItem programmatically
The service routine should be called from the ViewModel
EDIT:
your code of the service routine should be placed there:
set {
selectedItem = value;
// your code
RaisePropertyChanged(() => SelectedItem);
}
Another valid approach is to use Blend on XAML, by invoking a command on changed index and process under the ViewModel.
To do this, first add reference to System.Windows.Interactivity in your project and in XAML add
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity
Then modify ListView with the following:
<ListView Name="lstEditInstance"
Grid.Row="5"
ItemsSource="{Binding Path=InstanceList}"
Width="488"
FontFamily="Arial" FontSize="11"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Margin="10,96,0,28"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}}">
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="SelectionChanged">
<interactivity:InvokeCommandAction Command="{Binding YourCommand}"
CommandParameter="{Binding YourCommandParameter}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
Why ComboBox SelectionChanged event fire when we changed value of combobox in WPF?
<telerik:RadComboBox Name="onClockComboBox" Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2" Width="200" Height="30" StaysOpenOnEdit="True" IsSynchronizedWithCurrentItem="True" OpenDropDownOnFocus="True"
ItemsSource="{Binding TravelOtherModel.OnClockOptionListCollection,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center"
DisplayMemberPath="OptionName" SelectedValuePath="RowNo" IsFilteringEnabled="True" IsEditable="True" Margin="80,3,0,0">
<ie:Interaction.Triggers>
<ie:EventTrigger EventName="SelectionChanged">
<ie:InvokeCommandAction Command="{Binding OnClockComboBoxSelectedItemChangedCommand}" CommandParameter="{Binding ElementName=onClockComboBox}" >
</ie:InvokeCommandAction>
</ie:EventTrigger>
</ie:Interaction.Triggers>
</telerik:RadComboBox>
SelectionChanged event Code is -
public void OnClockComboBoxSelectedItemChangedCommand_Event(object obj)
{
try
{
RadComboBox onClockComboBox = obj as RadComboBox;
var item = onClockComboBox.SelectedItem as OnClockOption;
string s= onClockComboBox.SelectedValue.ToString();
onClockComboBox.SelectedValue = "0";
}
catch (Exception ex)
{
}
}
When we set default selected value by below code then its again firing SelectionChanged event why?
onClockComboBox.SelectedValue = "0";
When we set default 'select' value by below code then its again firing
selection Changed event why?
Because it should, otherwise how will you get notified if the combo box is reset. Assuming your selected value is bound to load some other control. It would require to know if the selection has changed even for the default value.
I have an existing solution of my WPF UI but it's ViewModel implementation is clunky and I'm looking to improve.
Below is a gif of how my current system works:
There's a Current Task (note: only ever one item)
There's a Task List for Tasks (note: possibly many) that need to run in the future
When the user selects one list box, the other selection is removed
The problem is, I'm implementing Current Task as a Listbox with only one item. This means I have to lug around a backing IList for the ItemSource and another property for the SelectedItem.
Is there another control I can use to behave like ListBoxItem, but I can bind my CurrentTask directly to it and not have to muck around with an List for ItemSource as well?
EDIT: To get the selection to go away when one listbox is selected, I have a trigger set up on the SelectionChanged event.
(deleted my previous answer)
It occurs to me that at least part of the functionality you're looking for is implemented by the RadioButton class. Multiple RadioButtons in the same scope guarantee that only one of them is selected. You'll probably have to do a little work to make sure that your RadioButtons can be scoped correctly in your UI, and you'll probably need to retemplate some things to get exactly the UI you need. Additionally, RadioButton does not have a SelectedItem/SelectValue property to which it can write to, because WPF provides no built-in mechanism for multiple controls to safely bind to a "SelectedWhatever" property. But you could roll this yourself pretty easily with codebehind or triggers.
Here's the implementation I went with:
XAML View
<!-- The Current Task box -->
<ListBox x:Name="CurrentTaskBox" FlowDirection="RightToLeft" Background="{StaticResource WhiteBrush}">
<ListBoxItem IsSelected="{Binding CurrentTaskSelected, Mode=TwoWay}" Content="{Binding CurrentTask.TaskId}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Selected">
<command:EventToCommand Command="{Binding SetTaskDetailsFromCurrentTaskCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBoxItem>
</ListBox>
<!-- The Task List box -->
<ListBox x:Name="TaskListBox" SelectedIndex="{Binding TaskListIndex}" SelectedValue="{Binding TaskListSelection}" IsSynchronizedWithCurrentItem="True" HorizontalContentAlignment="Stretch" ItemsSource="{Binding TaskList}" FlowDirection="RightToLeft" DisplayMemberPath="TaskId" Margin="3">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding SetTaskDetailsFromTaskListCommand}" CommandParameter="{Binding SelectedItem, ElementName=TaskListBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
ViewModel
/* Omitted most INPC property declarations...kinda boring */
public ICommand SetTaskDetailsFromCurrentTaskCommand { get { return new RelayCommand(SetTaskDetailsFromCurrentTask); } }
public ICommand SetTaskDetailsFromTaskListCommand { get { return new RelayCommand<TaskScheduleSequenceDto>(async taskSelection => await SetTaskDetailsFromTaskList(taskSelection)); } }
private bool _currentTaskSelected;
public bool CurrentTaskSelected
{
get
{
return _currentTaskSelected;
}
set
{
Set(() => CurrentTaskSelected, ref _currentTaskSelected, value);
}
}
private async Task SetTaskDetailsFromTaskList(TaskScheduleSequenceDto taskListSelection)
{
if (taskListSelection == null)
{
return;
}
var taskDetails = await _broker.RetrieveTaskDetails(taskListSelection.TaskId);
TaskDetails = taskDetails;
CurrentTaskSelected = false;
}
private void SetTaskDetailsFromCurrentTask()
{
TaskDetails = CurrentTask;
TaskListSelection = null;
CurrentTaskSelected = true;
}
This works fine and only requires that I have a single CurrentTask property in my VM, which I think is much cleaner.
I have the following view.xaml and I bind a collection(SavedTracksCollection from viewmodel) to this list box and it displays the items in UI.
<phone:PanoramaItem Name="MusicTracks" Header="Saved Tracks" >
<Grid>
<ListBox x:Name="list" ItemsSource="{Binding SavedTracksCollection}" SelectedItem="{Binding SelectedItemTrack,Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Background="Red" >
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding TrackTitle}"/>
<TextBlock Text="{Binding TrackUri}"/>
</StackPanel>
</Button>
<DataTemplate>
</ListBox.ItemTemplate>
</Grid>
</phone:PanoramaItem>
And the i have the following property defined in my viewmodel(this viewmodel is set as data context for my view) for the selecteditem binding "SelectedItemTrack".And i am binding SavedTracksCollection to the itemsource of the list.
private SavedTracksModel _SelectedItemTrack;
public SavedTracksModel SelectedItemTrack
{
get {
return _SelectedItemTrack;
}
set
{
if (value!=null)
_SelectedItemTrack = value;
//RaisePropertyChanged("SelectedItemTrack"); I dont think we need this.Let me know otherwise.
}
}
private List<SavedTracksModel> _SavedTracksCollection = new List<SavedTracksModel>();
public List<SavedTracksModel> SavedTracksCollection
{
get
{
return GetSavedTracks();
}
set
{
this._SavedTracksCollection = value;
RaisePropertyChanged("SavedTracksCollection");
}
}
But i am not able to determine how do i capture the SelectedITem event when user selectes an item from the Listbox .Currently it doesn't trigger the set method of the SelectedITemTrack .Once i capture the event with the details of selected item binding "TrackUri" i want to go to a new page where i can play the track.
any idea how to fix the issue ?
The first solution I can think of, why not just use the SelectionChanged event on ListBox?
<ListBox x:Name="list" ItemsSource="{Binding SavedTracksCollection}"
SelectedItem="{Binding SelectedItemTrack,Mode=TwoWay}"
SelectionChanged="List_OnSelectionChanged"/>
// in code behind
private void List_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
// navigate here after validating the selected item
// or raise Command in your ViewModel programatically
}
Alright I tried my best but looks like I need help. I have a textbox, a listview and a button in my xaml file. Listview has two columns: Devicename and DeviceAddress. I have done a binding of both the listview and textbox in such a way, that whenever I select an item in listview(I2CDeviceList), the deviceaddress(2nd Column) gets displayed in my textbox.
XAML:
<TextBox PreviewTextInput="AddressBox_PreviewTextInput" Name="AddressI2C" Text="{Binding SelectedItem.I2CDeviceAddress, Path=AddressMessage, Mode=TwoWay, ElementName=I2cDeviceList}" />
<Button Content="I2C Read" Command="{Binding Path=I2CReadCommand}" Name="button9" />
<ListView Grid.Column="0" ItemsSource="{Binding I2CDeviceList}" SelectedItem="{Binding SelectedI2CDeviceList, Mode=TwoWay}" Height="100" HorizontalAlignment="Stretch" Name="I2cDeviceList" VerticalAlignment="Stretch" Width="Auto" >
<ListView.View>
<GridView>
<GridViewColumn Header="I2C Device" Width="Auto" DisplayMemberBinding="{Binding I2CDevName}" />
<GridViewColumn Header="I2C Device Address" Width="Auto" DisplayMemberBinding="{Binding I2CDeviceAddress}" />
</GridView>
</ListView.View>
</ListView>
Thus using SelectedItem.I2CDeviceAddress gives me the deviceaddress in my Textbox.
Now my view model has a property for the Button and the textbox and has the following method which gets invoked when button is clicked:
public void I2CReadCommandExecuted()
{
ReadMessage = string.Empty;
Byte[] buffer = new Byte[512];
int address;
string strValue = AddressMessage;
if (strValue.StartsWith("0x"))
{
strValue = strValue.Remove(0, 2);
address = Convert.ToInt32(strValue);
mComm.setAddress(address);
}
}
// This is for textBox
private string _AddressMessage = string.Empty;
public string AddressMessage
{
get
{
return _AddressMessage;
}
set
{
_AddressMessage = value;
NotifyPropertyChanged("AddressMessage");
}
}
// Property for ListView
public ObservableCollection<I2CModel> I2CDeviceList
{
get { return _I2CDeviceList; }
set
{
_I2CDeviceList = value;
NotifyPropertyChanged("I2CDeviceList");
}
}
// Property for Selected Item in ListView
private I2CModel _selectedI2CDeviceList;
public I2CModel SelectedI2CDeviceList
{
get { return _selectedI2CDeviceList; }
set
{
_selectedI2CDeviceList = value;
NotifyPropertyChanged("SelectedI2CDevSize");
}
}
Basically I have to remove the 0x from the value and store the hexadecimal value in my integer variable.
Here I am facing two issues:
When I put both Text="{Binding SelectedItem.I2CDeviceAddress, Path=AddressMessage, Mode=TwoWay, ElementName=I2cDeviceList}" the seelcted address from the listview doesnt appear in my textbox. The moment I remove Path=AddressMessage, Mode=TwoWay,, it works fine. How to make sure both of them work smoothly? Is their any other way I can get the selected item from the listview and display it in my textbox?
By using string strValue = AddressMessage; I am trying to save the content of AddressMessage in the string but when I debug my code, it always shows "null" even though I have "0x23"(hardcoded) in my textbox. Due to this I get the following error: Object reference not set to an instance of an object. at the beginning of if condition.
I tried my level best but it ain't happening. Am i missing something?
First of all there is no need to have seperate AddressMessage property. It can be done using SelectedI2CDeviceList. But still if you want to use it it can be achieved through below changes -
Set AddressMessage property when the selected item of listview changes
public I2CModel SelectedI2CDeviceList
{
get { return _selectedI2CDeviceList; }
set
{
_selectedI2CDeviceList = value;
AddressMessage = _selectedI2CDeviceList.I2CDeviceAddress;
NotifyPropertyChanged("SelectedI2CDevSize");
}
}
Also change the binding of textbox to below one:
<TextBox
Name="AddressI2C"
Text="{Binding Path=AddressMessage, Mode=TwoWay}" />
Hence whenever selected item of the listview changes it will set the content for textbox and when AddressMessage property is properly set you want get your second issue.
Hope this helps.