Data binding not giving expected results windows phone - c#

In my windows phone app I've implemented data binding which is not yielding me expected results.
My functionality is I've a list box in which I've two textboxes which are data bound.
When I click the textbox datepicker/timepicker will open and the selected value should reflect in the textbox.
The xaml code for the listbox data template is as follows
<TextBox Visibility="{Binding TBVisibility}" IsReadOnly="{Binding TBReadOnly}" InputScope="{Binding Numeric}" AcceptsReturn="{Binding MultiLine}" Width="{Binding TBWidth}" TextWrapping="Wrap" Text="{Binding Path=TBText, Mode=TwoWay}" GotFocus="TextBox_GotFocus_1" KeyUp="TextBox_KeyUp_1" LostFocus="TextBox_LostFocus_1" />
<TextBox Visibility="{Binding TB2Visibility}" IsReadOnly="True" Width="140" Text="{Binding TB2Text, Mode=TwoWay}" GotFocus="TextBox_GotFocus_2" />
I'm launching the datepicker and timepicker as follows
private void LaunchDatePicker(TFDetails field)
{
datePicker = new CustomDatePicker
{
IsTabStop = false,
MaxHeight = 0,
Value = field.SelectedDate
};
datePicker.DataContext = field;
datePicker.ValueChanged += DatePicker_ValueChanged;
LayoutRoot.Children.Add(datePicker);
datePicker.ClickDateTemplateButton();
}
Where as "field" is the datacontext of the listbox.
The ValueChanged events are as follows
private void DatePicker_ValueChanged(object sender, DateTimeValueChangedEventArgs e)
{
DatePicker currentDP = sender as DatePicker;
TFDetails callingField = currentDP.DataContext as TFDetails;
if (callingField != null)
{
callingField.SelectedDate = currentDP.Value;
callingField.TBText = currentDP.ValueString;
}
}
When I change the time its not reflecting in the textbox. I wrote INotifyChangedProperty also.
May I know what mistake I could possible be doing here.
I actually have the same code in a similar UI page where it works perfectly. i don't know what mistake I'm doing here.
Thanks.

ListBox is a collection control. If you have a DataTemplate for it, bindings in it will use a single elements of collection boud to ItemsSource as a DataContext rather than DataContext od entire ListBox.

Related

How do I handle SelectionChange when the ComboBox is

I have a ListBox, where the list element has a ComboBox, a TextBox and a slider. Depending on the selction of the ComboBox either the TextBox or the slider should be visible.
<ListBox Name="lstPWM" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<!-- more definitions -->
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding GeberVisible}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding WertVisible}" Value="{Binding Wert, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind is:
public partial class MainWindow : Window
{
public ObservableCollection<PWMKanal> PWM_col { get; set; } = new();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lstPWM.ItemsSource = PWM_col;
foreach (var item in Board.PWM) PWM_col.Add(item); //Board.PWM is the data source.
}
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox box = sender as ComboBox; // Finding the line in the ListBox.
PWMKanal PWM = box.DataContext as PWMKanal;
int z = PWM_col.IndexOf(PWM);
Board.PWM[z].Gebertyp = (QuellePWM)box.SelectedValue;
if (Board.PWM[z].Gebertyp == QuellePWM.Sender)
{
PWM_col[z].GeberVisible = Visibility.Visible; // I thought that i may change the
PWM_col[z].WertVisible = Visibility.Hidden; // ObservableColelction directly
} // but the display is not updated.
else // In Debug mode i see, that PWM_coll
{ // is changed as expected, but no effect
PWM_col[z].GeberVisible = Visibility.Hidden; // on the GUI.
PWM_col[z].WertVisible = Visibility.Visible;
}
if (PWM_col.Count != 0) // this code is intended to update the GUI, but every time
{ // a new item is added the Selection Change fires again
PWM_col.Clear(); // and i get a stack overflow in an endless loop.
foreach (var item in Board.PWM) PWM_col.Add(item);
}
}
}
The comments describe my approaches and problems:
I change the selected element of the ObservableCollection directly, but this has no effect on GUI. At least tho code doesn't crash.
I clear the list ObservableCollection PWM_col, but then i get an infinite loop: every time an element is added to the list the SelectionChange event fires, calling the routin again. Result is stack overflow.
Now my questions to my approaches:
Is it possible to change an element of an ObservableCollection directly by code, and the display is automatically refreshed?
Is it possible to somehow catch the SelectionChanged event before the handler is executed? Or is it possible to temporary dissable the event?
Any other idear?
Thank you for your help!
CollectionChanged does notify, that collection itself, not the
single items, is changed. Therefore to see the changes item's
property need to implement INotifyPropertyChanged. Also remove Mode=OneTime
You can of course set the flag, that PWMTyp_SelectionChanged is
running:
private bool selChangedIsRunning = false;
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(selChangedIsRunning) return;
selChangedIsRunning = true;
// do stuff ....
selChangedIsRunning = false;
}
Other idea is - don't use the SelectionChange event, but do bind
Slider.Visibility and TextBox.Visibility to the
ComboBox.SelectedValue and use value converter to define the
Visibilty, also you can use the ConverterParameter.
<ComboBox x:Name="CmbPWMTyp" ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=TBX}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=SLDR}" Value="{Binding Wert, Mode=TwoWay}"/>
This link can be also very helpful for you: Difference between SelectedItem SelectedValue and SelectedValuePath

How set the property of WPF DataGrid (mvvm) in "MouseDown" event based on "sender"?

I'm having some trouble to set one property (MouseDown event) value based on it's sender. I have "MyPhotoA" and "MyPhotoB" binded to an observableCollection. Both trigger the same event "MyOnClick" Here is the xaml:
... stuff
<DataTemplate>
<Image Source="{Binding MyPhotoA, UpdateSourceTrigger=LostFocus}" MouseDown="MyOnClick" />
</DataTemplate>
... stuff
<DataTemplate>
<Image Source="{Binding MyPhotoB, UpdateSourceTrigger=LostFocus}" MouseDown="MyOnClick" />
</DataTemplate>
... stuff
These two datatemplates are used for two datagridtemplatecolumns in the datagrid. Hence there are two columns of images and the user clicks one. I want to set the source on the image clicked.
The event "MyOnClick" is something like this:
private void MyOnClick(object sender, MouseButtonEventArgs e)
{
var myImage File.ReadAllBytes("c:\\MyImage.jpeg")
var dc = (sender as System.Windows.Controls.Image).DataContext;
MyModelClass itemSelected = (MyModelClass)dc;
itemSelected.PhotoA = myImage;//Setting PhotoA
itemSelected.PhotoB = myImage;//Setting PhotoB
//How to set the photo based on "sender" property? Like:
//sender.[somestuff]=myImage;
}
I'd like to use the same method to set data in PhotoA and PhotoB based on the sender property binded to it. So if user click in the "PhotoA" DataGrid cell, the image is setted to "PhotoA". If click is done in "PhotoB" then "PhotoB" data is setted.
!!!Note!!!: I don't want tricks like
If (sender.name="PhotoA") then
itemSelected.PhotoA = myImage;
else
itemSelected.PhotoB = myImage;
Thanks in advance
[Workaround Update]
I could not find the answer so I used a workaround:
1)edit xaml code, adding a property "name" to each Photo:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="ImageMyPhotoA" Source="{Binding Photo}" MouseDown="MyOnClick" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
On the event, I manually added the bin to the the observable collection.
private void MyOnClick(object sender, MouseButtonEventArgs e)
{
var myImage = File.ReadAllBytes("c:\\MyImage.jpeg");
var dc = (sender as Image).DataContext;
MyModelClass itemSelected = (MyModelClass)dc;
var senderName = (sender as Image).Name;
if (senderName == "ImagePhotoA")
{
itemSelected.PhotoA = myImage;
}
if (senderName == "ImagePhotoB")
{
itemSelected.PhotoB = myImage;
}
}
Conclusion
Setting properties in "MouseDown" event based on Sender (Sender.[SomeSenderProperty] = "Something") seems not possible OR over complicated. I suggest to mark the sender's name in xaml (like the example). Thanks for the good fellows for your help, I really appreciate.
You're essentially trying to set the source property of an image the user clicked.
When you do that you want it to persist, presumably, and you probably won't want to overwrite the binding so make your binding twoway.
<Image Source="{Binding MyPhotoA, Mode=TwoWay}"
In your click handler.
Cast your sender to image.
var img = sender as Image;
(You should routinely null check when you do as anything.)
But this gives you a reference to the appropriate image control to work with.
Set the value.
As Clemens points out, I was overcomplicating this with:
img.SetCurrentValue(SourceProperty, Abitmapimage);
And you can just do:
img.Source = new BitmapImage(new Uri(#"C:\MyImage.jpeg"));

DevExpress Grid: notification for property-of-property works for xaml declared gridcolumn but not for programmatically-added gridcolumn

I have built a UserControl containing a DevExpress GridControl that has its GridColumns added programmatically when a "UCKit" (contains various parameters for the UserControl, including column specs) is bound to the control. One of the columns is bound to the Count property of an ObservableCollection that is a property of the class making up the GridControl's ItemsSource, and does not respond to PropertyChanged notifications, or even to a brute-force update attempt made by calling grid.RefreshData(). I have written a test program that uses simple GridColumn declarations in xaml, and the Count column works fine, but I can't get the grid whose GridColumns are built in code to update.
Here's the xaml that updates successfully:
<dxg:GridControl Grid.Column="0" HorizontalAlignment="Stretch" Name="griddywiddy2" AutoGenerateColumns="None"
Grid.Row="2" Grid.ColumnSpan="4" VerticalAlignment="Stretch" ItemsSource="{Binding MyDataColl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<dxg:GridControl.View>
<dxg:TableView AllowPerPixelScrolling="True" ShowTotalSummary="True"/>
</dxg:GridControl.View>
<dxg:GridControl.Columns>
<dxg:GridColumn Header="Row Name" Width="Auto" FieldName="RowName"/>
<dxg:GridColumn Header="Count" Width="Auto" FieldName="Stringz.Count"/>
</dxg:GridControl.Columns>
</dxg:GridControl>
And the xaml + c# code that doesn't (Row Name updates, Count does not):
<dxg:GridControl Grid.Column="0" HorizontalAlignment="Stretch" Name="griddywiddy" AutoGenerateColumns="None"
Grid.Row="1" Grid.ColumnSpan="4" VerticalAlignment="Stretch" ItemsSource="{Binding MyDataColl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<dxg:GridControl.View>
<dxg:TableView AllowPerPixelScrolling="True" ShowTotalSummary="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
private void InitializeColumns()
{
griddywiddy.Columns.Add(
new DevExpress.Xpf.Grid.GridColumn()
{
Header = "Row Name",
Binding = new Binding("RowName")
{
Mode = BindingMode.TwoWay,
Converter = null
},
Width = 100
});
griddywiddy.Columns.Add(
new DevExpress.Xpf.Grid.GridColumn()
{
Header = "Count",
Binding = new Binding("Stringz.Count")
{
Mode = BindingMode.OneWay,
Converter = null
},
Width = 100
});
}
My two methods of updating, both of which work for both xaml-declared columns and for the code-built Row Name column, are (called in button click handlers in my test program):
private void UpdBtn_Click(object sender, RoutedEventArgs e)
{
griddywiddy.RefreshData();
griddywiddy2.RefreshData();
}
private void UpdBtn2_Click(object sender, RoutedEventArgs e)
{
MyData incer = MyDataColl[0];
incer.NotifyPropertyChanged("Stringz.Count");
incer.NotifyPropertyChanged("RowName");
}
I'm truly befuddled as to why this Count property works properly in the xaml case but not the code-built case. Can anyone advise, please?
It has come to my attention that using Binding = new Binding("Stringz.Count") in the creation of the GridColumn (what I do that fails) is NOT quite equivalent to setting the GridColumn's FieldName="Stringz.Count" (what I do in the xaml, which works.) So for these "nested" notifications (which call for a complex path), use the FieldName method of specifying the GridColumn.
This solution came courtesy of the folks at DevExpress, btw, big thanks to them!

c# How to get Listbox selection from Observable Collection

I'm probably not even asking this correctly, I am new to c#, but trying to help my 14 year-old son learn. I've created a listbox with items created with an ObservableCollection. Here is the XAML:
<ListBox x:Name="listBox1" ItemsSource="{Binding}" Margin="105,205,886,63"
IsTabStop="True" SelectionChanged="PrintText"
ScrollViewer.VerticalScrollBarVisibility="Hidden" TabIndex="5" FontSize="36"
Background="Transparent" Foreground="#FF55B64C" FontFamily="Arabic Typesetting"
FontWeight="Bold" IsDoubleTapEnabled="False" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="blockNameList" Text="{Binding name}"/>
<TextBlock Text=" #"/>
<TextBlock Name="blockIdList" Text="{Binding id}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is how I created the ListBox Items:
var client = new HttpClient();
var uri = new Uri("http://theurlImusing");
Stream respStream2 = await client.GetStreamAsync(uri);
// DataContractJsonSerializer ser2 = new DataContractJsonSerializer(typeof(RootObject));
// RootObject feed2 = (RootObject)ser2.ReadObject(respStream2);
DataContractJsonSerializer ser = null;
ser = new DataContractJsonSerializer(typeof(ObservableCollection<RootObject>));
ObservableCollection<RootObject> feed2 = ser.ReadObject(respStream2) as ObservableCollection<RootObject>;
var cardList = new List<RootObject>();
foreach (RootObject returnfeed in feed2)
{
string cid = returnfeed.id;
string cardname = returnfeed.name;
listBox1.Items.Add(new RootObject { id=cid, name=cardname });
}
I thought I would just use the SelectionChanged="PrintText" property of the listbox so that when I clicked on a listbox item, it would just change a textblock's text value. Ultimately, that is all I am trying to do...set a textblock or textbox to be equal to the "id" value that is clicked on in the ListBox.
void PrintText(object sender, SelectionChangedEventArgs args)
{
//What do I put in here??
}
Thanks very much for any insight! I need it!!
This is something that is much easier to do using data binding. You can bind the TextBlock.Text property directly to the ListBox using an ElementName binding:
<TextBox Text="{Binding ElementName=listBox1,Path=SelectedItem.id}" />
Alternatively, if you set set SelectedValuePath="id" on the ListBox, then binding to SelectedValue will give you the "id" property:
<ListBox x:Name="listBox1" SelectedValuePath="id" ... />
<TextBox Text="{Binding ElementName=listBox1,Path=SelectedValue}" />
As a side note (as #Rachel already noted in comments): you may as well just set the ItemsSource, rather than looping through and adding each manually. All you need is this:
listBox1.ItemsSource = feed2;
Edit
Ok, if you wanted to use the procedural approach, here's how you would do it. (No one would recommend this approach, especially if you're learning/teaching. Try to make full use of data binding, and view-viewmodel separation.)
void PrintText(object sender, SelectionChangedEventArgs args)
{
var listBox = (ListBox)sender;
RootObject selectedItem = listBox.SelectedItem;
someTextBox.Text = selectedItem.id;
}
If all you want to do is click an item in the ListBox and get it to show up in the TextBox, you don't need fancy binding (in that other answer) to do it. You can simply add a MouseUp event in the ListBox XAML:
MouseUp="ListBox1_MouseUp"
This would work similar to the SelectionChanged event you wanted to use.
You then right-click that function name in the XAML page and select "Go to definition". It will create the next function for you:
private void ListBox1_MouseUp(object sender, MouseButtonEventArgs e)
{
}
Simply add in there to update the TextBox you want with the SelectedItem values from sender:
private void ListBox1_MouseUp(object sender, MouseButtonEventArgs e)
{
ListBox lstBox = (ListBox)sender;
ListBoxItem item = lstBox.SelectedItem;
if (item != null) // avoids exception when an empty line is clicked
{
someBox.Text = item.name;
someOtherBox.Text = item.id;
}
}
I later found that blockNameList and blockIdList are not accessible via intellisense because they are within the DataTemplate of the ListBox, so I put someBox and someOtherBox, as references to other TextBoxes you would have to add to the XAML, outside of the ListBox. You would not re-write data inside the ListBox on the same item by clicking it. Even if you could reach the template's TextBlock to do it, you'd just be re-writing that same item with its own values, since it would be the SelectedItem!
Even though there are those that don't recommend this approach because they like binding everything - and in some cases you want binding to occur so that controls on the page update as a result of dependencies (i.e. do one thing to cause another), I find that manual methods of clicking a button/item/control to update something are just fine and avoid all the model/MVVM BS that has taken over WPF and over-complicated it.

WPF: ListView SelectedItem binding works for a split second, then changes back

let me start by introducing my current setup:
I have a ListView that binds its SelectedItem property to the ViewModel, like this:
<ListView Name="FileListView" ItemsSource="{Binding ImageList}"
SelectionChanged="ImageSelectionChanged"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<view:FileListItem />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It's item template (view:FileListItem) is the following:
<Grid MouseDown="FileListItemMouseDown" KeyDown="FileListItemKeyDown">
....
<TextBlock Name="NewNameTextBlock"
Text="{Binding NewName}"
Grid.Column="2"
Visibility="{Binding TextBlockVisibility}" />
<TextBox Name="NewNameTextBox"
Text="{Binding NewName, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="2"
Visibility="{Binding TextBoxVisibility}" />
</Grid>
The idea here is to switch on the TextBox and switch off the TextBlock when the corresponding ListView item is being edited. This works ok, but when I hit a particular key, I want the ListView to select the next item and put that item into editing mode. I catch the KeyDown event as seen above in the ItemTemplate and broadcast a message, which is caught in the DataContext of my ListView like this:
public ImageFile SelectedItem {
get { return _selectedItem; }
set { _selectedItem = value; NotifyPropertyChanged("SelectedItem"); }
}
public void SelectAndEditThisHandler (object x)
{
ImageFile file = x as ImageFile;
SelectedItem = file;
}
The result is that the selection actually changes for a split second, but then it changes back to the previous selection. I suspect some other UI elements might be handling my key-presses and doing something to change the selection back, but I can't figure out which elements and how to pinpoint them.
Any help would be greatly appreciated! Thanks!
EDIT:
As requested, the SelectionChanged handler:
private void ImageSelectionChanged(object sender, SelectionChangedEventArgs e)
{
System.Collections.IList filelist = FileListView.SelectedItems;
if (filelist.Count == 1)
{
ImageFile selectedFile = FileListView.SelectedItem as ImageFile;
Mediator.Instance.NotifyColleagues(Mediator.Operations.ImagePathSelected, selectedFile.OriginalPath);
}
}
The mediator message broadcast doesn't do anything related to these controls/this problem at all.

Categories