I have created a combobox in xaml like this:
ComboBox x:Name="cbTest" SelectedValue="{Binding TestSpeed}" HorizontalAlignment="Left" Margin="0,10,0,0" Width="250" SelectionChanged="cbTest_SelectionChanged"/>
And the Combobox is filled with the following items:
for (int i = 1; i < 6; i++)
cbTest.Items.Add(i);
I see the items in the combobox, but it doesn't show the SelectedValue what I choose before. This is the property:
private short _testSpeed;
public short TestSpeed
{
get
{
return _testSpeed;
}
set
{
_testSpeed= value;
NotifyPropertyChanged();
}
}
And this is when I change the item on SelectedChanged
_vm.TestSpeed = (short)Convert.ToInt16(cbTest.SelectedValue);
TestSpeed gives me the correct data in debug, but the selectedValue binding isn't working!?
In your situation binding mode must be OneWayToSource like SelectedValue="{Binding Path=TestSpeed, Mode=OneWayToSource} . When TestSpeed is string or int that is work, when short - not work. I think you need to write specific converter for using short or using int and dont worry
Related
What I want
So I want to check 2 ObservableCollections if they equals each other.
If so, then return Nothing Changed (collection1 and collection2 are the same).
Otherwise return Something Changed.
The Problem
The problem now is, that both collection contains the same values even when I change items from collection 2.
I posted some Code and gif of the Debug result to show you what I get.
I dont understand, why both Collections are the same after clicking the Save Button.
Code
ViewModel
In my ViewModel I have:
1 ObservableCollection called RightsCollection.
This should contain the rights on my XAML which I can change via ToggleButton.
1 Employee class where a ObservableCollection<Groups> is located and inside of the Groups.Col there is a ObservableCollection<Rights> which contains the default group rights which was loaded from DataBase which cant be changed.
Note: My get set is always the same. They just have other names and DataTypes consider to its field datatype.
private Employee _singleEmployee = new Employee();
public Employee SingleEmployee
{
get => _singleEmployee;
set
{
if (_singleEmployee == value) return;
_singleEmployee = value;
OnPropertyChanged("SingleEmployee");
}
}
private ObservableCollection<Groups> _groupsCollection = new ObservableCollection<Groups>();
// public get set GroupsCollection (same like first).
private ObservableCollection<Rights> _rightsCollection = new ObservableCollection<Rights>();
// public get set RightsCollection (same like first).
Employee Class
public class Employee : INotifyPropertyChanged
{
private int _employeeId;
private string _firstName;
private Groups _group = new Group();
// public get set EmployeeId (Same like first).
// public get set Group (same like first).
}
Rights Class
private int _rightId;
private string _rightName;
private bool _hasRight;
// Again get set is same
Groups Class
private int _groupId;
private string _groupName;
private ObservableCollection<Rights> _rights;
// Again, same Get/Set like always
XAML
In my XAML I have:
a ComboBox. ComboBox.ItemsSource bind to GroupsCollection. ComboBox.SelectedValue bind to SingleEmployee.Group.
So while changing the ComboBox, the Group of the Single Employee will be set.
This ComboBox also got an SelectionChanged Event where I set the RightsCollection equal to SingleEmployee.Group.Rights. So that both contains the same items/values now.
It also contains an ItemsControl where I can set the rights myself (and where the rights will be loaded when ComboBox.SelectionChanged (which works).
<ComboBox x:Name="GroupComboBox" ItemsSource="{Binding GroupsCollection}" SelectedValue="{Binding SingleEmployee.Group}" DisplayMemberPath="GroupName" SelectionChanged="GroupComboBox_SelectionChanged">
ItemsControl
<ItemsControl ItemsSource="{Binding RightsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<DockPanel>
<ToggleButton DockPanel.Dock="Right" Margin="10" IsChecked="{Binding HasRight}"/>
<TextBlock FontSize="15" FontWeight="Bold" Text="{Binding RightName}" DockPanel.Dock="Left" Margin="10" />
</DockPanel>
<TextBlock Text="{Binding RightsDesc}" Margin="30 0 0 10" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
SelectionChanged Event in Code-Behind
Debug.WriteLine("############ SelectionChanged Event ############");
Debug.WriteLine("# Before Change ##");
Debug.WriteLine($"SingleEmployee.Group.Rights.Count: {_viewModel.SingleEmployee.Group.Rights.Count} | RightsCollection.Count: {_viewModel.RightsCollection.Count}");
for (int i = 0; i < _viewModel.SingleEmployee.Group.Rights.Count; i++)
{
Debug.WriteLine($"Name: {_viewModel.SingleEmployee.Group.Rights[i].RightName}, HasRight: {_viewModel.SingleEmployee.Group.Rights[i].HasRight} || Name: {_viewModel.RightsCollection[i].RightName}, HasRight: {_viewModel.RightsCollection[i].HasRight}");
}
_viewModel.RightsCollection = _viewModel.SingleEmployee.Group.Rights;
Debug.WriteLine("# After Change #");
Debug.WriteLine($"SingleEmployee.Group.Rights.Count: {_viewModel.SingleEmployee.Group.Rights.Count} | RightsCollection.Count: {_viewModel.RightsCollection.Count}");
for (int i = 0; i < _viewModel.SingleEmployee.Group.Rights.Count; i++)
{
Debug.WriteLine$"Name: {_viewModel.SingleEmployee.Group.Rights[i].RightName}, HasRight: {_viewModel.SingleEmployee.Group.Rights[i].HasRight} || Name: {_viewModel.RightsCollection[i].RightName}, HasRight: {_viewModel.RightsCollection[i].HasRight}");
}
Debug.WriteLine("########## SelectionChanged Event END ##########");
Debug.WriteLine("################################################");
Set ViewModel in Code-Behind
private readonly EmployeeViewModel _viewModel;
// constructor...
{
_viewModel = (EmployeeViewModel) DataContext;
}
Save Button Command Method
Debug.WriteLine("############## After Button Click ##############");
for (int i = 0; i < RightsCollection.Count; i++)
{
Debug.WriteLine($"Name: {SingleEmployee.Group.Rights[i].RightName}, HasRight: {SingleEmployee.Group.Rights[i].HasRight} || Name: {RightsCollection[i].RightName}, HasRight: {RightsCollection[i].HasRight}");
}
Debug.WriteLine("################################################");
bool equal = RightsCollection.Count == SingleEmployee.Group.Rights.Count && RightsCollection.All(x => SSingleEmployee.Group.Rights.Contains(x));
Debug.WriteLine(equal ? "Nothing Changed" : "Something changed");
What I tried
SelectionChanged Event
// No Success
var collection = new ObservableCollection<Rights>(_viewModel.SingleEmployee.Group.Rights);
_viewModel.RightsCollection = collection;
.
// No Success
foreach(var item in _viewModel.SingleEmployee.Group.Rights)
_viewModel.RightsCollection.Add(item);
Result of the Debugging
SelectionChangedResult
|
SelectionChangedResult
After Button Click
After Button Click
_viewModel.RightsCollection = _viewModel.SingleEmployee.Group.Rights;
the left collection has the same reference of the right collection.
So, if you change one collection it will reflect in the other collection.
ObservableCollection<Whatever> _viewModel.RightsCollection = new ObservableCollection<Whatever>();
foreach(var item in _viewModel.SingleEmployee.Group.Rights)
_viewModel.RightsCollection.Add(item);
I fixed this problem in my SelectionChanged Event deleting this line:
_viewModel.RightsCollection = _viewModel.SingleEmployee.Group.Rights;
and replaced it with this:
for (int i = 0; i < _viewModel.SingleEmployee.Group.Rights.Count; i++)
{
if (_viewModel.SingleEmployee.Group.Rights[i].HasRight != _viewModel.RightsCollection[i].HasRight)
{
_viewModel.RightsCollection[i].HasRight = _viewModel.SingleEmployee.Group.Rights[i].HasRight;
}
}
Because both collections are nearly the same, they will always have the same amount of items so I can use a for-loop.
If a value is not the same, then the value will change.
This way I don't create a reflection (I guess) so it's working.
The only thing now is, that
bool equal = RightsCollection.Count == SingleEmployee.Group.Rights.Count && RightsCollection.All(x => SingleEmployee.Group.Rights.Contains(x));
isn't working but I will use a for-loop here too which checks if the items contains the same value, if not then "Something changed".
This is for a Windows 10 Universal App.
XAML:
<RelativePanel Padding="4" Margin="4,12,0,0">
<TextBlock x:Name="Label" Text="Class Name" Margin="12,0,0,4"/>
<ListView x:Name="ClassTextBoxes"
ItemsSource="{Binding TextBoxList}"
SelectionMode="None" RelativePanel.Below="Label">
<ListView.ItemTemplate>
<DataTemplate >
<RelativePanel>
<TextBox x:Name="tbox"
PlaceholderText="{Binding PlaceHolder}"
Text="{Binding BoxText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Padding="4" Width="200" MaxLength="25"/>
<TextBlock x:Name="errorLabel"
RelativePanel.Below="tbox"
Text="{Binding Error, Mode=TwoWay}"
Padding="0,0,0,4"
FontSize="10"
Foreground="Red"/>
<Button Content="Delete" Margin="12,0,0,0" RelativePanel.RightOf="tbox"/>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RelativePanel>
Model:
public class TextBoxStrings : BaseModel
{
private string _placeholder;
public string PlaceHolder
{
get { return _placeholder; }
set
{
if (_placeholder != value)
{
_placeholder = value;
NotifyPropertyChanged();
}
}
}
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
}
}
public string CheckBoxText(string val)
{
var r = new Regex("[^a-zA-Z0-9]+");
return r.Replace(val, "");
}
}
ViewModel:
private TrulyObservableCollection<TextBoxStrings> _textBoxList;
public TrulyObservableCollection<TextBoxStrings> TextBoxList
{
get { return _textBoxList; }
set
{
if (_textBoxList != value)
{
_textBoxList = value;
RaisePropertyChanged();
}
}
}
and I add new TextBoxString objects to my TextBoxList collection from within my view-model.
I want to make it that users can't type in certain characters (or rather, they get deleted whenever they
are typed in.
This works...in the model. Setting breakpoints and looking at the values, everything in the Model is working: value goes into the setter and gets changed, _boxText holds the new value that is set from CheckBoxText();
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
So if I type in "abc*()" into "tbox", the value in the model will be "abc". The value of the textbox, however, will still be "abc*()".
I have a feeling it has something to do with the fact that I'm editing items that are inside of a collection and I don't have anything implemented to handle changing items within a collection. I was under the impression that using INotifyPropertyChanged and ObservableCollection<T> would take care of that for me.
Does anyone have any suggestions?
Thank you!
Edit: So, now I'm trying to use TrulyObservableCollection because I thought this was the problem, but it hasn't helped. Here it is: https://gist.github.com/itajaja/7507120
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
As you've seen, the TextBox do reflect changes to your model. When you type in "abc*()" in the TextBox, the value in the model will be changed to "abc". The problem here is that the binding system in UWP is "intelligent". For TwoWay bindings, changes to the target will automatically propagate to the source and in this scenario, binding system assumes that the PropertyChanged event will fire for corresponding property in source and it ignores these events. So even you have RaisePropertyChanged or NotifyPropertyChanged in you source, the TextBox still won't update.
In WPF, we can call BindingExpression.UpdateTarget Method to force the update. But this method is not available in UWP.
As a workaround, you should be able to use TextBox.TextChanged event to check the input like following:
private void tbox_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = sender as TextBox;
if (tb != null)
{
var originalText = tb.Text;
var r = new Regex("[^a-zA-Z0-9]+");
if (originalText != r.Replace(originalText, ""))
{
var index = (tb.SelectionStart - 1) < 0 ? 0 : (tb.SelectionStart - 1);
tb.Text = r.Replace(originalText, "");
tb.SelectionStart = index;
}
}
}
However it may break your MVVM model, you can use data validation to avoid this and here is a blog: Let’s Code! Handling validation in your Windows Store app (WinRT-XAML) you can refer to. And for my personal opinion, data validation is a better direction for this scenario.
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
Try changing this to:
var tmp = CheckBoxText(value);
if (_boxText != tmp)
{
_boxText = tmp;
NotifyPropertyChanged();
}
I hope, in your XAML, the binding to property BoxText is two-way, right?
You should edit BoxText and then send checked value to UI. Just send value to CheckBoxText and already edited should be assigned to _boxText. And then you should send BoxText to UI by calling RaisePropertyChanged("BoxTest"). Please, see the following code snippet:
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText=CheckBoxText(value);
RaisePropertyChanged("BoxText");
}
}
}
There is no difference where you use INotifyPropertyChanged for one property of for properties placed in collection. The complete example with collections and ListView can be seen here
I want to populate a ComboBox based on selection of other ComboBox.
Both combo boxes are populate from database using WCF.
My problem is that on first selection it's not working (just after second selection it's work and it's show results from first selection).
XAML
<ComboBox
x:Name="selClientCombo"
SelectionChanged="listEchipamente"
IsEditable="True"
SelectedIndex="-1"
HorizontalAlignment="Left"
Margin="455,35,0,0"
VerticalAlignment="Top"
Width="215"
ItemsSource="{Binding Mode=OneWay}"/>
<ComboBox
x:Name="selEchipamentCombo"
HorizontalAlignment="Left"
Margin="457,65,0,0"
VerticalAlignment="Top"
Width="213"
ItemsSource="{Binding}"/>
code
private void listEchipamente(object sender, SelectionChangedEventArgs e)
{
List<string> echipamenteWCF = client.getEchipament(selClientCombo.Text).ToList();
MessageBox.Show(" Client Selected !");
if (selEchipamentCombo.Items.Count >0)
{
selEchipamentCombo.Items.Clear();
}
for (int i = 0; i < echipamenteWCF.Count(); i++)
{
selEchipamentCombo.Items.Add(echipamenteWCF[i]);
}
}
At the time SelectionChanged is fired, the Text has not been updated (and hence it holds the previous value).
You should access the underlying data item to get the Text instead:
if(selClientCombo.SelectedItem == null) return;
List<string> echipamenteWCF =
client.getEchipament(selClientComo.SelectedItem.ToString()).ToList();
...
I supposed the ToString() will resolve the display Text. You can always cast SelectedItem to the actual type and access its string property (being shown as Text) easily. You can also access the SelectedValue with condition that some SelectedValuePath is set for the ComboBox.
I'm using Caliburn Micro on a Windows Store App.
I have a ListView which has a SelectedItem that works the first time I use it. However, when I clear the ListView and re-bind it to another Collection, the selected item no longer appears selected.
The selectedItem property is being set correctly, since I can hit the breakpoint, and everything works has expected, just the View is not being updated with the selected item, after I clear the collection.
What could be wrong?
Thanks.
Edit:
View Code:
<ListView x:Name="DetailNotes"
ItemsSource="{Binding DetailNotes}"
SelectedItem="{Binding SelectedDetailNote}"
ItemTemplate="{StaticResource Notes600ItemTemplate}"
IsItemClickEnabled="True"
caliburn:Message.Attach="[Event ItemClick] = [DetailNoteSelected($eventArgs)]"/>
ViewModel Code:
(...)
private Note selectedDetailNote;
public Note SelectedDetailNote
{
get { return this.selectedDetailNote; }
set
{
this.selectedDetailNote = value;
this.NotifyOfPropertyChange(() => this.SelectedDetailNote);
}
}
(...)
public void DetailNoteSelected(ItemClickEventArgs eventArgs)
{
Note n = (Note)eventArgs.ClickedItem;
this.SelectedDetailNote = n;
}
Sorry! The problem was my explicit binding. I just left Caliburn do his work, and now it works!
Solution below:
View Code:
<ListView x:Name="DetailNotes"
ItemTemplate="{StaticResource Notes600ItemTemplate}"/>
ViewModel Code:
private Note selectedDetailNote;
public Note SelectedDetailNote
{
get { return this.selectedDetailNote; }
set
{
this.selectedDetailNote = value;
this.NotifyOfPropertyChange(() => this.SelectedDetailNote);
}
}
I know it's late, but your problem was binding mode. You should set it to TwoWay:
SelectedItem="{Binding SelectedDetailNote, Mode=TwoWay}"
In WinRT XAML default is OneWay.
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.