Update text boxes when a property's value changes - WPF - c#

The context of this example is there are four text boxes that hold a total amount of time. 1 for hours, 1 for minutes, 1 for seconds, and 1 for milliseconds.
There is a fifth text box that holds the total time in just milliseconds. This can be seen in the image below.
I have made an implementation of IMultiValueConverter that should convert 4 TextBox components and the converted value in a property. It should also be able to update the 4 boxes when the property's value changes.
When the user types in the text box that holds the converted output and then that box loses focus, the other 4 text boxes are updated. However, when the property's value is programmatically changed, in this case by a button click, the values in the 4 text boxes are not updated.
How can I make these 4 text boxes update through the converter?
The ultimate goal, in this example, is to store the total time (in milliseconds) in a property and have 5 text boxes updating through bindings when that property is updated.
This is the code for the converter.
using System;
using System.Globalization;
using System.Windows.Data;
namespace MultiBinding_Example
{
public class MultiDoubleToStringConverter : IMultiValueConverter
{
private const double HOURS_TO_MILLISECONDS = 3600000;
private const double MINUTES_TO_MILLISECONDS = 60000;
private const double SECONDS_TO_MILLISECONDS = 1000;
private const string ZERO_STRING = "0";
private object valBuffer = null;
/*
* values[0] is the variable from the view model
* values[1] is hours
* values[2] is the remaining whole minutes
* values[3] is the remaining whole seconds
* values[4] is the remaining whole milliseconds rounded to the nearest millisecond
*/
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
object returnVal = ZERO_STRING;
try
{
if (values != null)
{
double hoursToMilliseconds = (values[1] == null || values[1].ToString() == string.Empty) ? 0 : System.Convert.ToDouble(values[1]) * HOURS_TO_MILLISECONDS;
double minutesToMilliseconds = (values[2] == null || values[2].ToString() == string.Empty) ? 0 : System.Convert.ToDouble(values[2]) * MINUTES_TO_MILLISECONDS;
double secondsToMilliseconds = (values[3] == null || values[3].ToString() == string.Empty) ? 0 : System.Convert.ToDouble(values[3]) * SECONDS_TO_MILLISECONDS;
double totalTime = ((values[4] == null || values[4].ToString() == string.Empty) ? 0 : System.Convert.ToDouble(values[4])) + secondsToMilliseconds + minutesToMilliseconds + hoursToMilliseconds;
returnVal = totalTime.ToString();
if (values[0] == valBuffer)
{
values[0] = returnVal;
}
else
{
valBuffer = returnVal = values[0];
ConvertBack(returnVal, new Type[] { typeof(string), typeof(string), typeof(string), typeof(string), typeof(string) }, parameter, culture);
}
}
}
catch (FormatException) { }
return returnVal;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
try
{
if (value != null && value.ToString() != string.Empty)
{
double timeInMilliseconds = System.Convert.ToDouble(value);
object[] timeValues = new object[5];
timeValues[0] = value;
timeValues[1] = Math.Floor(timeInMilliseconds / HOURS_TO_MILLISECONDS).ToString();
timeValues[2] = Math.Floor((timeInMilliseconds % HOURS_TO_MILLISECONDS) / MINUTES_TO_MILLISECONDS).ToString();
timeValues[3] = Math.Floor((timeInMilliseconds % MINUTES_TO_MILLISECONDS) / SECONDS_TO_MILLISECONDS).ToString();
timeValues[4] = Math.Round(timeInMilliseconds % SECONDS_TO_MILLISECONDS, MidpointRounding.AwayFromZero).ToString();
return timeValues;
}
}
catch (FormatException) { }
return new object[] { ZERO_STRING, ZERO_STRING, ZERO_STRING, ZERO_STRING, ZERO_STRING };
}
}
}
To test this, I have a quite a simple layout that consists of a few Label components, a few TextBox components and a Button.
It looks like this.
The XAML for it is the following.
<Window x:Class="MultiBinding_Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MultiBinding_Example"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MultiDoubleToStringConverter x:Key="multiDoubleToStringConverter"/>
</Window.Resources>
<StackPanel>
<Label Content="Multi Value Converter" HorizontalAlignment="Center" FontSize="35" FontWeight="Bold" Margin="0, 25, 0, 0"/>
<Label Content="Formatted Total Time" FontWeight="Bold" FontSize="24" Margin="20, 10"/>
<Grid Margin="80, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox Name="Hours" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Text="0" Grid.Column="0"/>
<Label Content="Hours" Grid.Column="1" Margin="0, 0, 15, 0"/>
<TextBox Name="Minutes" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Text="0" Grid.Column="2"/>
<Label Content="Minutes" Grid.Column="3" Margin="0, 0, 15, 0"/>
<TextBox Name="Seconds" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Text="0" Grid.Column="4"/>
<Label Content="Seconds" Grid.Column="5" Margin="0, 0, 15, 0"/>
<TextBox Name="Milliseconds" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Text="0" Grid.Column="6"/>
<Label Content="Milliseconds" Grid.Column="7"/>
</Grid>
<Label Content="Unformatted Total Time" FontWeight="Bold" FontSize="24" Margin="20, 10"/>
<Grid Margin="80, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Grid.Column="0">
<TextBox.Text>
<MultiBinding Converter="{StaticResource multiDoubleToStringConverter}" Mode="TwoWay">
<Binding Path="TotalTime"/>
<Binding ElementName="Hours" Path="Text"/>
<Binding ElementName="Minutes" Path="Text"/>
<Binding ElementName="Seconds" Path="Text"/>
<Binding ElementName="Milliseconds" Path="Text"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
<Label Content="Milliseconds" Grid.Column="1"/>
</Grid>
<Button Grid.Column="1" Margin="250, 20" Height="50" Content="Random Total Milliseconds" Click="RandomTime_Click"/>
</StackPanel>
</Window>
The code behind is the following.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace MultiBinding_Example
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Random random = new Random();
private string totalTime;
public string TotalTime {
get => totalTime;
set {
totalTime = value;
RaisePropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
UpdateTotalTime();
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void RandomTime_Click(object sender, RoutedEventArgs e)
{
UpdateTotalTime();
}
private void UpdateTotalTime()
{
double percent = random.NextDouble();
double time = Math.Floor(percent * random.Next(1000, 100000000));
TotalTime = time.ToString();
}
}
}

This isn't really what a converter is for.
Converters take a set of view model values and convert them to view values for display. Then if the view values change it can convert them back to view model values.
In your case, the view model value is updated through code (not through a change to the view) and so the converter has no reason to run the ConvertBack method (the value is already a view model value!). This is one of several reasons why converters should not have side-effects.
The correct way to do this would be to have TotalTime as a property on the VM (probably as a number or TimeSpan and not a string as you have it) and then do individual converters for each of the pieces. For example:
<TextBox Text="{Binding TotalTime, Converter={StaticResource TimeSecondsConverter}"/>
The main text box would then just be bound to TotalTime. TimeSecondsConverter would probably need to be a multi-value converter in order for ConvertBack to work.

Related

WPF: How to autoreset/autorefresh the textbox after scanning barcode in WPF?

I'm making a simple WPF program where the user will basically only need to scan the barcode using a USB barcode scanner and the program will send the data straight to the cloud ERP. Right now, the program works, but the user has to manually click the textbox after scanning, clear the data and scan again. I just want the user to open the software and just keep on scanning using his barcode reader without having to manually erase the textbox data. How can I do that?
<Window x:Class="ProductionScanner.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModel="clr-namespace:ProductionScanner.MVVM.ViewModel"
xmlns:local="clr-namespace:ProductionScanner"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Background="#181735" >
<Window.DataContext>
<viewModel:ReceiptViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Background="#0F0F2D"
Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Foods"
Foreground="White"
Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Left"
/>
<TextBlock Text="Records"
Foreground="White"
Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
/>
</Grid>
<StackPanel FocusManager.FocusedElement="{Binding ElementName=txtIniFocus}">
<TextBox x:Name="txtIniFocus" Margin="6" Text="{Binding Barcode, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" Width="150" Grid.Row="1" TextChanged="TextBox_TextChanged">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ReceiptCommand}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</Grid>
And, the ReceiptViewModel.cs:
using ProductionScanner.Cores;
using ProductionScanner.MVVM.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace ProductionScanner.MVVM.ViewModel
{
public class ReceiptViewModel: ObservableObject
{
private string _barcode;
public string Barcode
{
get { return _barcode; }
set { _barcode = value; OnPropertyChanged(); }
}
public RelayCommand ReceiptCommand { get; set; }
public ReceiptViewModel()
{
ReceiptCommand = new RelayCommand(x =>
{
receiptInventory();
});
}
private void receiptInventory()
{
if (Barcode.Length == 12)
{
var inventoryIDFrom = Barcode.Substring(0, 6);
var kgIntFrom = Barcode.Substring(6);
var kgDecimalFrom = kgIntFrom.Substring(0,kgIntFrom.Length-4) + "." + kgIntFrom.Substring(kgIntFrom.Length - 4, 3);
ERPRestService _erpRestService = new ERPRestService();
ReceiptItems receiptItems = new ReceiptItems();
receiptItems.InventoryID = inventoryIDFrom;
receiptItems.Qty = Convert.ToDecimal(kgDecimalFrom);
receiptItems.WarehouseID = "WH001";
receiptItems.LocationID = "L001";
receiptItems.LotSerialNbr = "";
receiptItems.ExpirationDate = DateTime.Now;
try
{
var jsonObj1 = new
{
Description = new
{
value = "Receipt"
},
Hold = new
{
value = false
},
Details = new[]
{
new {
InventoryID = new
{
value = receiptItems.InventoryID
},
Qty = new
{
value = receiptItems.Qty
},
WarehouseID = new
{
value = receiptItems.WarehouseID
},
Location = new
{
value = receiptItems.LocationID
}
}
}
};
string entityAsString = JsonConvert.SerializeObject(jsonObj1);
//string parameters1 = "$expand=Details";
var receipt = _erpRestService.Put("InventoryReceipt", null, entityAsString);
string refRelease = JObject.Parse(receipt)["ReferenceNbr"]["value"].ToString();
var release = new
{
entity = new
{
ReferenceNbr = new
{
value = refRelease
}
}
};
string jsonRelease = JsonConvert.SerializeObject(release);
jsonRelease = _erpRestService.Post("InventoryReceipt", "ReleaseInventoryReceipt", jsonRelease);
MessageBox.Show("Your AR Confirm has completed.", "Confirm", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
throw (ex);
}
finally
{
_erpRestService.Dispose();
}
}
}
}
}
I tried a simplified version of the markup and code.
The messagebox looked a bit suspicious to me as I was thinking it might take focus. What might happen after that.
Use of return was also a concern as I wondered what else return might do.
There's nowhere else for the focus to go.
If I just press tab then the cursor is still in the textbox.
My experimental markup:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Background="#0F0F2D"
Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Foods"
Foreground="White"
Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Left"
/>
<TextBlock Text="Records"
Foreground="White"
Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
/>
</Grid>
<StackPanel FocusManager.FocusedElement="{Binding ElementName=txtIniFocus}">
<TextBox x:Name="txtIniFocus" Margin="6" Text="{Binding Barcode, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" Width="150" Grid.Row="1">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ReceiptCommand}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</Grid>
</Window>
My viewmodel
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private string barcode = "65765765";
[RelayCommand]
private async Task Receipt()
{
// Your receipt processing would go here
MessageBox.Show("Your AR Confirm has completed.", "Confirm", MessageBoxButton.OK, MessageBoxImage.Information);
Barcode=string.Empty;
}
}
I hit enter, the messagebox shows. I click OK. The messagebox closes, textbox is empty and the cursor is in the textbox.
If what you've shown us was all you have then I think setting Barcode to string.Empty would probably work.
Not sure it'd make much difference but my property changed notification is from the community toolkit mvvm and using the code generator. It will check for equality before raising property changed.
I'm not sure what your observableobject is there.
I still think the messagebox is a bad idea as the user has to pick the mouse up and click. An on screen indicator would be something to consider instead.
FWIW
I would also recommend just checking the length is 12 and drive the processing that way.
If you do both then the user could just scan, the receipt is done, scan the next receipt is done, scan....
They wouldn't have to touch keyboard or mouse.
That's the approach I've used for warehouse and product scanning on what sound like similar apps.

UWP Object reference not set to an instance of an object when adding item to Azure easy table

I have a popup window that takes in input from a user and then should send it to a model which then POSTS it to an azure easy table. When I build the project everything runs fine until I hit the submit button then the app crashes and I get the Null Exception Object reference not set to an instance of an object.
XAML for input:
<Popup x:Name="ppup" IsOpen="False" IsLightDismissEnabled="True"
Width="320" HorizontalAlignment="Left">
<Popup.ChildTransitions>
<TransitionCollection>
<!--<EdgeUIThemeTransition Edge="Left" />-->
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<Grid Width="380" Height="{Binding ElementName=flyoutPane, Path=Height}" Background="{ThemeResource FlyoutBackgroundThemeBrush}" >
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,10,10,10" >
<TextBlock Name="NameText" Text="Enter Name:"/>
<TextBox Name="NameBox" Width="200" Height="50"/>
<TextBlock Name="SetsText" Text="Enter Sets:"/>
<TextBox Name="SetsBox" Width="200" Height="50"/>
<TextBlock Name="TimeText" Text="Enter Time to complete:"/>
<TextBox Name="TimeBox" Width="200" Height="50"/>
<Button Name="SubmitBtn" Height="30" Width="100" Content="Submit" Click="SubmitBtn_Click"/>
</StackPanel>
</Grid>
</Popup>
C# for handling input and passing it to model:
CombatTableView ctv = new CombatTableView();
private async void SubmitBtn_Click(object sender, RoutedEventArgs e)
{
DrillItem drillItem = new DrillItem();
String Name = NameBox.Text;
int Sets = Int32.Parse(SetsBox.Text);
int Time = Int32.Parse(TimeBox.Text);
await ctv.combatDrillsTable.AddDrill(drillItem, Name, Sets, Time, parameters);
ppup.IsOpen = false;
var dialog = new MessageDialog("Your message here");
await dialog.ShowAsync();
}
View Model:
class CombatTableView
{
public CombatDrillsTable combatDrillsTable { get; set; }
public CombatTableView()
{
this.combatDrillsTable = new CombatDrillsTable();
}
}
Model for interacting with database:
public async Task AddDrill(DrillItem drillItem, String n, int s, int t, string sty)
{
drillItem.Name = n;
drillItem.Sets = s;
drillItem.SetTime = t;
drillItem.Style = sty;
await App.MobileService.GetTable<DrillItem>().InsertAsync(drillItem);
drills.Add(drillItem);
}
In my case the problem was due to a checkbox accidentally bound to the event instead of the property:
<CheckBox x:Name="chkIsActive" Checked="{Binding IsActive}" />
instead of
<CheckBox x:Name="chkIsActive" IsChecked="{Binding IsActive}" />
There was a null value inside combatDrillsTable and the list that was to hold the objects null too because i hadn't put anything into my easy table at that stage so it was throwing a null exception.

How to display array in xaml under UWP C#

How to display array in xaml the way that each line is on separate row and each row is split into separate textblocks?
I split line like so:
string[] splitLines = line.Split(';');
itemsControl.Items.Add(line);
Then, in xaml I display it like so:
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding itemsControl}"
FontSize="24">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto"
Margin="0 12"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk0" Text="{Binding }" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This way I get each line from my .xls file in separate row, but the separator ';' is included in the returned content of row.
Now I would like to remove ';' separator and put each word from each line on separate textblock, like so:
for (int index = 0; index < splitLines.Length; index++)
{
itemsControl.Items.Add(splitLines[index]);
}
This breaks each line into separate words, but unfortunately, each word is put on separate row(instead to populate 5 textblocks with each line's words.
Any idea how to show each .xls line in separate row, but then also split each line into words and put them in separate textblocks in separate columns?
EDIT
I have tried the solution from Alexej Sommer, I get some erors though:
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0];
value1 = splitLines[1];
value2 = splitLines[2];
value3 = splitLines[3];
value4 = splitLines[4];
}
items.Add(dataitem);
In this piece of code I get first semicolon underscored and error says:
} expected.
Then, values 1 - 4 are underscored and error says:
The name (X) does not exist in the current content
At last, dataitem gets underscored and same error shows up.
EDIT 2 --
I managed to fix the problem. There were problems with braces and code put in a wrong place. Also, instead of:
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0];
value1 = splitLines[1];
value2 = splitLines[2];
value3 = splitLines[3];
value4 = splitLines[4];
}
items.Add(dataitem);
I used:
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0],
value1 = splitLines[1],
value2 = splitLines[2],
value3 = splitLines[3],
value4 = splitLines[4],
};
items.Add(dataitem);
And that solved the problem.
Here is clean working code:
public async void ReadFile()
{
var path = #"CPU.xls";
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path);
var readFile = await Windows.Storage.FileIO.ReadLinesAsync(file);
foreach (string line in readFile.OrderBy(line =>
{
int lineNo;
var success = int.TryParse(line.Split(';')[4], out lineNo);
if (success) return lineNo;
return int.MaxValue;
}))
{
string[] splitLines = line.Split(';');
ObservableCollection<ItemsData> items = new ObservableCollection<ItemsData>();
for (int index = 0; index < splitLines.Length; index++)
{
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0],
value1 = splitLines[1],
value2 = splitLines[2],
value3 = splitLines[3],
value4 = splitLines[4],
};
items.Add(dataitem);
}
itemsControl.DataContext = items;
}
}
I yet need to fix the layout as for now all the textblocks show up in same column and they overlap.
Thank you Alexej for your help!
Edit 3----
To put textblocks on separate columns in xaml I did like so:
<ScrollViewer>
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding}"
FontSize="24">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto"
Margin="0 12"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk0" Text="{Binding value0}" />
</StackPanel>
<StackPanel Grid.Column="1"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk1" Text="{Binding value1}" />
</StackPanel>
<StackPanel Grid.Column="2"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk2" Text="{Binding value2}" />
</StackPanel>
<StackPanel Grid.Column="3"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk3" Text="{Binding value3}" />
</StackPanel>
<StackPanel Grid.Column="4"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk4" Text="{Binding value4}" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
This way each word from line is put on separate column. TextBlocks don't overlap anymore.
Unfortunately I didn't manage do showcase all file(all lines). For now each row in xaml is populated with same line from the file instead of pulling each line into separate rows in xaml.
What should I do in order to list all lines instead of just one line?
I believe that there is problem on C# code side.
First create class for data:
public class itemsdata
{
public string txt0 { get; set; }
public string txt1 { get; set; }
public string txt2 { get; set; }
public string txt3 { get; set; }
public string txt4 { get; set; }
}
after it, create collection
ObservableCollection<itemsdata> items = new ObservableCollection<itemsdata>();
and fill it:
for (int index = 0; index < splitLines.Length; index++)
{
itemsdata dataitem=new itemsdata
{
txt0=splitLines[0];
txt1=splitLines[1];
txt2=splitLines[2];
txt2=splitLines[3];
txt2=splitLines[4];
}
items.Add(dataitem);
}
itemsControl.DataContext = items;
change binging to
ItemsSource="{Binding}"
and add new fields to datatemplate
<TextBlock Name="txtblk0" Text="{Binding txt0}" />
<TextBlock Name="txtblk1" Text="{Binding txt1}" />
<TextBlock Name="txtblk2" Text="{Binding txt2}" />
<TextBlock Name="txtblk3" Text="{Binding txt3}" />
<TextBlock Name="txtblk4" Text="{Binding txt4}" />

Property binding not working in ListBox WPF

I am trying to fill a ListBox with items consisting of a grid which holds a checkbox and a textblock. I originally had only a checkbox but I need the text to wrap so I changed it to a grid with a checkbox and a textblock, with the text block doing the text wrapping.
When it was just the the checkbox, the binding worked, but now it does not. Now the correct number of items show up in the listbox but it is just an unchecked checkbox (even if I set it to checked in the code behind, see below) and a blank textblock.
XAML:
<ListBox
x:Name="lstCalibration"
Margin="304,95,78,24"
Background="#7FFFFFFF"
Width="211"
HorizontalAlignment="Center"
VerticalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="auto">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox
Content=""
Tag="{Binding Tag}"
IsChecked="{Binding IsChecked}"
Checked="CheckBoxCal_Checked"
Unchecked="CheckBoxCal_Unchecked"
Grid.Row="0" Grid.Column="0"
/>
<TextBlock
Tag="{Binding Tag}"
Text="{Binding Text}"
TextWrapping="Wrap"
Grid.Row="0"
Grid.Column="1"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind:
foreach (Controller item in calList)
{
//fill listbox with cals and correct checked states
Grid grid = new Grid();
CheckBox cb = new CheckBox();
TextBlock tb = new TextBlock();
tb.Text = item.Description;
cb.Tag = tb.Tag = i;
cb.IsChecked = calChecked[i];
if (cb.IsChecked == true)
noneChecked = false;
i++;
Grid.SetRow(cb, 0);
Grid.SetColumn(cb, 0);
grid.Children.Add(cb);
Grid.SetRow(tb, 0);
Grid.SetColumn(tb, 1);
grid.Children.Add(tb);
lstCalibration.Items.Add(grid);
}
Add this, and delete that foreach loop where you programatically create all the stuff you already set up in your DataTemplate. It's a perfectly good DataTemplate, but your code isn't using it right. Get rid of calChecked; _items[n].IsChecked will now do that job.
At any time you can add a new ListItem to _items and it will appear in the list, or remove an existing one and it will vanish from the list. That's the "observable" part. Thanks to the PropertyChanged event raising, if you set IsChecked on any of these from code behind, the corresponding checkbox in the UI will automagically update.
public class ListItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public String Text { get; set; }
private bool _isChecked = false;
public bool IsChecked {
get { return _isChecked; }
set {
_isChecked = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(IsChecked));
}
}
}
In your constructor in code behind:
private ObservableCollection<ListItem> _items;
public MyWindow()
{
InitializeComponent()
_items = new ObservableCollection<ListItem>(
calList.Select(
cal => new ListItem {
Text = cal.Description,
IsChecked = cal.WhateverElse
}
));
lstCalibration.Items = _items;
}
One small change to DataTemplate in two places: Don't waste time binding Tag. In the event handlers (which you may not even need), just cast like so:
public void CheckBoxCal_Checked(object sender, EventArgs args)
{
var listItem = ((FrameworkElement)sender).DataContext as ListItem;
// Do whatever. listItem.IsChecked will be up to date thanks
// to the binding.
}
XAML:
<CheckBox
Content=""
IsChecked="{Binding IsChecked}"
Checked="CheckBoxCal_Checked"
Unchecked="CheckBoxCal_Unchecked"
Grid.Row="0" Grid.Column="0"
/>
<TextBlock
Text="{Binding Text}"
TextWrapping="Wrap"
Grid.Row="0"
Grid.Column="1"
/>

How to Bind to index in ItemsControl from DataTemplate in silverlight

I have a Silverlight application that is using a ItemsControl. Inside of that ItemsControl I have a DataTemplate that is defined like the following:
XAML
<ItemsControl Grid.Row="1" Grid.ColumnSpan="5" x:Name="dgOdds">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="gRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="OF1" Grid.Column="0" Text="{Binding OddFactor, Mode=TwoWay}" FontWeight="Bold" VerticalAlignment="Center" HorizontalContentAlignment="Center"/>
<TextBox x:Name="OFX" Grid.Column="1" Text="{Binding OddFactor, Mode=TwoWay}" FontWeight="Bold" VerticalAlignment="Center" HorizontalContentAlignment="Center"/>
<TextBox x:Name="OF2" Grid.Column="2" Text="{Binding OddFactor, Mode=TwoWay}" FontWeight="Bold" VerticalAlignment="Center" HorizontalContentAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have a list of objects:
C#
ObservableCollection<TestObj> SomeList = new ObservableCollection<TestObj>;
SomeList.Add(new TestObj(){ OddType = 1, OddFakctor = 1.1 });
SomeList.Add(new TestObj(){ OddType = 2, OddFakctor = 2.2 });
SomeList.Add(new TestObj(){ OddType = 3, OddFakctor = 3.3 });
this.dgOdds.ItemsSource = this.Collection;
The TestObj class is as follows:
public class TestObj
{
public double OddType { get; set;}
public double OddFakctor { get; set; }
}
I want to display the properties in my controls something like:
If OddType equal 1, than show in TextBox x:Name="OF1",
if OddType equal 2, than show in TextBox x:Name="OFX",
if OddType equal 3, than show in TextBox x:Name="OF3"
How to do this?
since u want to show the OddType equals to 1 or 2 or 3, u can have only a single textblock.
As if 1 is selected, then one of the TextBox would have OF1 and others will be null.
private double _oddfactor;
public double OddFakctor
{
get
{
return _oddfactor;
}
set
{
_oddfactor = value;
OnPropertyChanged(() => OddFakctor);
}
}
if here u update the _OddFakctor , it will bind the text box with the value wat u r passing to that _oddfactor

Categories

Resources