Failing To Bind Multiple UI Components in WPF - c#

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.

Related

C# WPF SelectionChangedEvent - reset selection - better options?

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) }

Unable to reselect single item in listview

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>

How to get the value from a Template Column in a WPF DataGrid.

I got a data grid which includes a check box column. I made a modification to that form by using a multiselect checkbox to check all rows at once. And it worked. but i was unable to get the value from that checkbox column when the app is running because i was not sure how to access the data column. can anyone help me with a way to get the check box value (true/false).
This is what i did so far.
Code: xaml
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path= Id}" Header="Id" Visibility="Hidden"/>
<DataGridTextColumn Binding="{Binding Path= Category}" Header="Category" Width="320"/>
<!--<DataGridCheckBoxColumn Binding="{Binding Path= Check}" Width="*"/>-->
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox x:Name="headerCheckBox" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" HorizontalAlignment="Center" IsChecked="{Binding IsChecked, ElementName=headerCheckBox, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Code: C#
for (int i = 0; i < datagridview.Items.Count; i++)
{
ÇategoryData CD = (ÇategoryData)datagridview.Items[i];
if (CD.Check == true)
{
//it always returns false even checked
}
}
First of all, I fail to see what that value would be used for? I mean, the checkbox acts like a UI mechanism to select or unselect all the other boxes. But even if you actually need the value, why don't you bind it twoway to a ViewModel property?
Like this:
<CheckBox x:Name="headerCheckBox" Value="{Binding SelectAllCheckboxInTheViewModel}" />
Here are many things kind of weird in my opinion.
I don't really get why your rows' Checkbox is called "chkSelectAll"
I suggest you try to get the following: Each Checkbox can be Checked,UnChecked by themself. When you hit the Headers Checkbox, then all Columns Checkbox get the same State (Checked if at least one was unchecked, and Unchecked if ALL Rows Checkboxes were checked).
If that is right, than you should do the following:
As Siderite Zackwehdex mentioned (I thing that he meant that), you should bind the Checkbox IsChecked Value to a Property of the underlying ViewModel of the Row.
Than your Headers Checkbox should be also bind to a Property of the Viewmodel which holds the Observable Collection like the following:
public bool AllSelected
{
get { return !this.MyCollection.Any(item => !item.IsSelected); }
set
{
var toggle = this.MyCollection.Any(item => !item.IsSelected);
foreach (var itm in this.MyCollection.Where(item => item.IsSelected != toggle))
itm.IsSelected = toggle;
}
}
The IsSelected-Property-Setter of the Collections Items-ViewModel MUST notify the ParentViewModel (which holds the Collection) that one IsSelected/IsChecked (however you want to call it) State has changed. So that a PropertyChanged Event for PropertyName "AllSelected" will be raised.
The Collection Items ViewModel Property could look like this:
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
ParentViewModel.RaisePropertyChanged("IsSelected");
}
}
Both CheckBoxes (The one in the Header Template and the one in the CellTemplate) are bind with Mode=Twoway

secondary listbox not show data

i've two listbox binded with properties of my viewmodel, the first listbox shows LstEmpresas and work fine, when I select a item, the property SelectedCompany sets fine, all ok.
In SelectedCompany's set of my viewmodel, I call a method than pupulate a secondary list (LtsEjercicios) and work fine too (LtsEjercicios populate perfectly depends that item i've selected in the first listbox).
The secondary listbox binds his ItemSource from LtsEjercicios object that, across viewmodel, is updated.
But the secondary listbox NOT SHOW any data, i'm crazy yet.
This viewModel code
public class frmEmpresasVM : ViewModelBase
{
//propiedades
EmpresasDataAccess empresasController = new EmpresasDataAccess();
private ObservableCollection<EmpresasTable> lstEmpresas;
private ObservableCollection<EmpresasEjerciciosTable> ltsEjercicios;
public ObservableCollection<EmpresasTable> LstEmpresas
{
get
{
return lstEmpresas;
}
set
{
lstEmpresas = value; RaisePropertyChanged("LstEmpresas");
}
}
public ObservableCollection<EmpresasEjerciciosTable> LtsEjercicios
{
get
{
return ltsEjercicios;
}
set
{
ltsEjercicios = value; RaisePropertyChanged("LtsEjercicios");
}
}
//selected company in listbox
private int selectedCompany;
public int SelectedCompany
{
get
{
return selectedCompany;
}
set
{
selectedCompany = value;
LtsEjercicios = empresasController.SelectExercicesById(selectedCompany.ToString());
RaisePropertyChanged("SelectedCompany");
}
}
//main constructor, default values for lists
public frmEmpresasVM()
{
LstEmpresas = empresasController.SelectOnlyNames();
LtsEjercicios = empresasController.SelectExercicesById("0");
}
and, XAML for the view
<ListBox x:Name="companyList" HorizontalAlignment="Left" Height="205" Margin="20,30,0,0" VerticalAlignment="Top" Width="450" ItemsSource="{Binding LstEmpresas, Mode=OneWay}" SelectedValue="{Binding SelectedCompany, Mode=TwoWay}" SelectedItem="{Binding LtsEjercicios, Mode=OneWay}" SelectedIndex="0" DisplayMemberPath="Nombre" SelectedValuePath="Id" IsSynchronizedWithCurrentItem="True" SelectionChanged="companyList_SelectionChanged_1">
<ListBox.ItemBindingGroup>
<BindingGroup/>
</ListBox.ItemBindingGroup>
<ListBox.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</ListBox.DataContext>
</ListBox>
<TextBlock x:Name="lblEjercicio" HorizontalAlignment="Left" Height="20" Margin="475,10,0,0" TextWrapping="Wrap" Text="Ejercicios" VerticalAlignment="Top" Width="95"/>
<ListBox x:Name="excercicesList" HorizontalAlignment="Left" Height="205" Margin="475,30,0,0" VerticalAlignment="Top" Width="110" ItemsSource="{Binding LtsEjercicios, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Ejercicio" SelectedValuePath="Id" SelectedIndex="0" IsSynchronizedWithCurrentItem="True">
<ListBox.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</ListBox.DataContext>
Al Data provides from a MySQL Database from both tables (empresas y empresas_ejercicios).
My goal is, when user select a item in listbox1 (empresas) show exercices in listbox2 (empresas_ejercicios).
If exist other path to determine this operation, i'm all eyes!!!
Thanks in advance.
Not sure if this is the only problem in your code, but what you're doing by setting
<ListBox.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</ListBox.DataContext>
for both list boxes is creating a separate instance of the ViewModel for each list box. Now I guess if you make any changes in the first list box' ViewModel instance, these changes will not be reflected in the second list box because this one binds to a totally different ViewModel instance.
Instead, try globally binding the ViewModel to the whole window / page / user control / etc. (depending on whether you;re doing WPF, Windows Phone, etc.), and let the list boxes inherit it to ensure that only one ViewModel instance is involved:
<Window.DataContext>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM/>
</Window.DataContext>
EDIT:
Alternatively, you might as well instantiate the ViewModel once and store it as global resource:
<Window.Resources>
<InnovaCommerceHosteleryViewModels:frmEmpresasVM x:Key="Viewmodel" />
</Window.Resources>
and in the listboxes' DataContext just reference this global resouce:
<ListBox DataContext="{StaticResource Viewmodel}" ... />

How to access my datagrid control in my viewmodel from view?

Hi guys I want to access my datagrid control in my viewmodel.I know this is the incorrect way but I have to do that:
<DataGrid
Grid.Row="1"
Margin="10,10,0,0"
Height="200"
Width="500"
Grid.ColumnSpan="2"
Name="dg"
HorizontalAlignment="Left"
AutoGenerateColumns="False"
ItemsSource="{Binding SSID}"
>
<DataGrid.Columns>
<DataGridTextColumn Width="100" Header="Network ID" Binding="{Binding _networkID}"></DataGridTextColumn>
<DataGridTextColumn Width="100" Header="SSID" Binding="{Binding _ssid}"></DataGridTextColumn>
<DataGridTextColumn Width="100" Header="VLAN" Binding="{Binding _vlan}"></DataGridTextColumn>
</DataGrid.Columns>
void AddSSIDs()
{
var ssid = new SSIDPropertyClass();
ssid._networkID = SSID.Count + 1;
ssid._ssid = EnteredSSIDAC;
ssid._vlan = VlanSSID;
if (ACSelectedSecurityType=="Static WEP")
{
ssid._authenticationMode = ACSelectedSecurityType;
ssid._authentication = ACStaticWEPSelectedAuthentication;
ssid._staticWEPKeyType = ACStaticWEPSelectedKeyType;
ssid._staticWEPKeyLength = ACStaticWEPSelectedKeyLength;
ssid._staticWEPKey1 = StaticWEPKey1;
ssid._staticWEPKey2 = StaticWEPKey2;
ssid._staticWEPKey3 = StaticWEPKey3;
ssid._staticWEPKey4 = StaticWEPKey4;
SSID.Add(ssid);
}
else if(ACSelectedSecurityType=="WPA/WPA2 Personal")
{
ssid._authenticationMode = ACSelectedSecurityType;
ssid._wpaPersonalKeyAC = WpaACKey;
SSID.Add(ssid);
}
}
I want to display only that columns in Datagrid which are selected in if blocks and else if blocks .If the condition of first if block is satisfies than all the other columns present inother else if blocks should be hidden. Please tell me the way in which I can access datagrid directly in ViewModel or any other way by which I can achieve the same thing.Any help would be highly appreciable.
You Can bind colunm visibility prop to your viewmodel prop:
<DataGridTextColumn Width="100" Header="Network ID" Binding="{Binding _networkID}" Visibility="{Binding NetworkVisibility}"></DataGridTextColumn>
<DataGridTextColumn Width="100" Header="SSID" Binding="{Binding _ssid}" Visibilty="{Binding SSIDVisible, Converter={StaticResource SSIDVisible}}"></DataGridTextColumn>
In ViewModel
public Visibility NetworkVisibility
{
get {
if(condition) return Visibility.Visible;
else return Visibility.Collapsed;
}
}
or you can do your viewmodel props of type bool, then just use BoolToVisibilityConverter in XAML
public bool SSIDVisible
{
get {
if(condition) return true;
else return false;
}
}
And for this props you can use NotifyPropertyChanged (if its supposed to change dynamically) as in Andrew Stephens answer.
You could create properties which contain information about the column selection status, for example a bool value, and bind them to the Visible property of your column. Use a converter to convert from bool to Visibility.
You could expose a couple of boolean properties from your VM, indicating which set of columns to display, then bind the Visibility property of each column to the relevant property. You'll need to use the BooleanToVisibilityConverter to convert the boolean value to a Visibility value (Visible or Collapsed). Something like this:-
<Window.Resources>
<BoolToVisibilityConverter x:Key="boolToVisConv" />
</Window.Resources>
<DataGridTextColumn Visibility="{Binding ShowWep, Converter={StaticResource boolToVisConv}" ... />
<DataGridTextColumn Visibility="{Binding ShowWpa, Converter={StaticResource boolToVisConv}" ... />
Edit (some VM code as requested)
Your VM class should implement INotifyPropertyChanged, and its property setters must raise the PropertyChanged event when the value changes. This ensures that anything in the view bound to a property reacts (e.g. refreshes) when its value changes. A typical example of the INPC interface can be found see here. Based on this code, the ShowWpa property would look something like this:-
public class MyViewModel
{
private bool _showWpa;
public bool ShowWpa
{
get
{
return _showWpa;
}
set
{
if (_showWpa != value)
{
_showWpa = value;
NotifyPropertyChanged("ShowWpa");
}
}
}
//etc..
}
A bad practise, but since you want it to be done that way..
Pass it as a parameter to the ViewModel from the code behind of the view.

Categories

Resources