I'm looking for the best way to populate a check boxes from the following code. I have looked into Binding but not really sure where to go.
Here is the edited code that is working
private void dpDateSelection_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{
DateTime? date = dpDateSelection.SelectedDate;
logDate = date != null ? date.Value.ToString("yyyy-MM-dd") : null;
dpDateSelection.ToolTip = logDate;
LoadLogs(logDate);
}
private void LoadLogs(string ldate)
{
string[] logs = Directory.GetFiles(logPath + ldate, "*.ininlog");
InitializeComponent();
logList = new ObservableCollection<String>();
logList.Clear();
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf(".");
s = s.Substring(0, extPos);
logList.Add(s);
}
this.DataContext = this;
}
<ListBox x:Name="Logs" ItemsSource="{Binding logList}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" ToolTip="{Binding}" Tag="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You will want to start by using an ItemsControl instead of a StackPanel, since ItemsControls are automatically set up to display collections of things:
<ItemsControl ItemsSource="{Binding Logs}"/>
Note the use of ItemsSource. With the accompanying binding string, it basically says "Look for a property on the DataContext called "Logs" and put everything in it into this control".
Next you said you wanted this displayed as checkboxes, so we use an item template:
<ItemsControl ItemsSource="{Binding Logs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content={Binding .}/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This says "Use a checkbox for each Item in the ItemsSource". The DataTemplate can be a Grid or other collection control as well, so this is a really powerful feature in WPF. The "Binding ." just binds to the object itself (a string in this case, so we don't need a special path).
Finally, you need to set up a property to bind to in your view model:
ObservableCollection<String> Logs {get; set;}
You want an ObservableCollection so that when anything is added to or removed from the list, it automatically updates the UI. If you are going to be completely replacing the list (assignment), then you need to implement INotifyPropertyChanged and invoke the PropertyChanged event in that properties setter.
In your posted loop, you would add each log file to this property.
Also, make sure that somewhere you set the DataContext property of the XAML file (View) to your view model object. If everything is in code behind, use DataContext = this. Note that doing this is considered bad practice, and you should use a separate class (ViewModel).
You didn't mention what you wanted the CheckBoxes to do, so I haven't included anything related to that in my answer. You will likely want to abstract your logs into an object with a "Selected" property you can then bind the IsChecked property of the CheckBoxes to.
Obviously this is a lot to take in, please let me know if I can clarify anything or help further!
Update
You put the property in your ViewModel (DataContext). Whatever class that is, you write:
ObservableCollection<String> Logs {get; set;}
private void LoadLogs()
{
string[] logs = Directory.GetFiles(logPath + logDate, "*.ininlog");
foreach(string logName in logs)
{
string s = logName.Substring(logName.IndexOf(logDate) + logDate.Length + 1);
int extPos = s.LastIndexOf(".");
s = s.Substring(0, extPos);
//MessageBox.Show(s);
Logs.Add(s); //Add the parsed file name to the list
}
}
Related
I have a row in a grid with 5 textboxes, 2 of which are enabled by checkboxes. I am trying to dynamically add additional rows to the grid when a button is clicked. The eventhandler I added will only enable the textbox in the first row, but not in the current row (2nd). There is another eventhandler which handles the box in the first row, this is a new one. (BTW I only have part of the second row coded). Not sure if I should try making a template for the checkbox, and then use binding to the textbox? And if so, the instructions I've read on connecting the binding are vague and confusing. Or can I do the binding directly? Or ?
public partial class Window2 : Window
{
int currentColumn = 0;
int currentRow = 1;
int timesCalled = 1;
public Window2()
{
InitializeComponent();
}
private void AddLevelButton_Click(object sender, RoutedEventArgs e)
{
string level = this.Level.Content.ToString(); //label for the row
string[] splitLevel = level.Split(' ');
int levelNum = int.Parse(splitLevel[1]);
levelNum = timesCalled + 1;
int nextRow = currentRow + 1;
int nextColumn = currentColumn + 1;
Label levelLabel = new Label();
levelLabel.Content = "Level " + levelNum.ToString();
Grid.SetRow(levelLabel, nextRow);
Grid.SetColumn(levelLabel, currentColumn);
FlowGrid.Children.Add(levelLabel);
currentColumn++;
CheckBox antesBox = new CheckBox(); //the checkbox to enable the
antesBox.Name = "AntesBox"; //textbox which follows
antesBox.VerticalAlignment = VerticalAlignment.Bottom;
antesBox.HorizontalAlignment = HorizontalAlignment.Right;
antesBox.FontSize = 16;
antesBox.Width = 20;
antesBox.Height = 20;
antesBox.Checked += AntesBox_Checked1; //eventhandler
Grid.SetRow(antesBox, nextRow);
Grid.SetColumn(antesBox, currentColumn);
FlowGrid.Children.Add(antesBox);
nextColumn = ++currentColumn;
TextBox enterAntes = new TextBox(); //the textbox to be enabled
enterAntes.Name = "EnterAntes";
enterAntes.Margin = new Thickness(5, 0, 5, 0);
enterAntes.FontSize = 16;
enterAntes.FontFamily = new FontFamily("Verdana");
enterAntes.IsEnabled = false;
enterAntes.KeyDown += EnterAntes_KeyDown1; //tested; this works
Grid.SetRow(EnterAntes, nextRow);
Grid.SetColumn(EnterAntes, nextColumn);
FlowGrid.Children.Add(EnterAntes);
nextColumn = ++currentColumn;
}
private void enterAntes_KeyDown1(object sender, KeyEventArgs e)
{
int key = (int)e.Key;
e.Handled = !(key >= 34 && key <= 43 ||
key >= 74 && key <= 83 || key == 2);
}
private void AntesBox_Checked1(object sender, RoutedEventArgs e)
{
EnterAntes.IsEnabled = true;
}
You need to add following codes to enable text boxes.
Following is the xaml view of the datagrid.
<DataGrid x:Name="gvTest" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" Margin="86,204,0,0" VerticalAlignment="Top" Height="132" Width="436">
<DataGrid.Columns>
<DataGridTemplateColumn Header="TextBox 01">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txt01" Width="50" Text="{Binding TxtBox01}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 02">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox02" Width="50" Text="{Binding TxtBox02}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 03">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox03" Width="50" Text="{Binding TxtBox03}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 04">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox04" Width="50" IsEnabled="False" Text="{Binding TxtBox04}" Loaded="txtbox04_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 05">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox05" Text="{Binding TxtBox05}" Loaded="txtbox05_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Enable" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="chk01" Checked="chk01_Checked" IsChecked="{Binding IsActive}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
add the following codes to declare instance of required textboxes and declare observable collection.
TextBox txt04;
TextBox txt05;
ObservableCollection<TestItem> TestItemList = new ObservableCollection<TestItem>();
add the following codes to the loaded event of the required textboxes.
private void txtbox04_Loaded(object sender, RoutedEventArgs e)
{
txt04 = (sender as TextBox);
//txt04.IsEnabled = false;
}
private void txtbox05_Loaded(object sender, RoutedEventArgs e)
{
txt05 = (sender as TextBox);
}
Now, create a model class with following code segment in order to bind values to the datagrid.
public class TestItem
{
public string TxtBox01 { get; set; }
public string TxtBox02 { get; set; }
public string TxtBox03 { get; set; }
public string TxtBox04 { get; set; }
public string TxtBox05 { get; set; }
public bool IsActive { get; set; }
public TestItem()
{
IsActive = false;
}
}
I have used a button to add new rows to the datagrid. add the following codes to the button click to add rows.
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
TestItemList.Add(new TestItem());
gvTest.ItemsSource = TestItemList;
}
Finally, add the following codes to the checkbox checked event
CheckBox c = (sender as CheckBox);
if (c.IsChecked==true)
{
txt04.IsEnabled = true;
txt05.IsEnabled = true;
}
Hope this helps you to fulfill your requirement.
At the risk of perpetuating the wrong approach, it seems to me that the most direct way to address your specific need here is to fix your event handler so that it is always specific to the text box that corresponds to the checkbox in question. This is most easily done by moving the event handler subscription to below the declaration of the local variable enterAntes, and then use that variable in the event handler (i.e. so that it's capture by the anonymous method used as the event handler). For example:
TextBox enterAntes = new TextBox(); //the textbox to be enabled
antesBox.Checked += (sender, e) => enterAntes.IsEnabled = true;
Now, that said, I whole-heartedly agree with commenter Mark Feldman who suggests that the code you've written is not the right way to accomplish your goal in WPF.
I'm not sure I agree with the characterization "harder". That's such a loaded and subjective term, depending in no small part in what you find easy or hard. Being new to WPF, you almost certainly find the concepts of data binding and declarative XAML-based coding "hard", and direct, procedural code such as in your example "easy" (or at least "easier" :) ).
But he's absolutely right that in the long run, you will be better served by doing things "the WPF way". You may or may not wind up with much less code, but the WPF API is designed to be leveraged as much as possible from the XAML, and use code-behind minimally (and certainly not for the purpose to build the UI).
So what's all that mean for your code? Well, I ramble and it would be beyond the scope of a good, concise Stack Overflow answer for me to try to rewrite your entire code from scratch to suit the WPF paradigm. But I will offer some suggestions as to how I'd handle this.
First, forget the UI objects themselves for a moment. Write classes that describe the key characteristics of the UI as you want it to be, without being the UI itself. In this example, this could mean that you should have a list of rows. There should also be a class that defines what a single row looks like, e.g. with a bool property (to reflect the checkbox state) and a string property (to reflect the text box value). This is your "model"; i.e. each class is an individual model class, and at the same time you could consider the entire collection of classes as the model for your UI.
Now, go back to your UI and define it in XAML, not in code. There are several different ways to represent a list in the UI. Classes like ListBox, ListView, DataGrid, or even ItemsControl (the base class for many of the list-oriented controls). Bind the source of your list control to the model list you created in the previous step.
Define a DataTemplate (again, in XAML) for the type of class that is contained in the list. This will declare the UI for a single row in your list. Your template might look something like this:
<!-- Make sure you defined the "local" XML namespace for your project using the
xmlns declaration -->
<DataTemplate DataType="{x:Type local:MyRowModel}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text}" IsEnabled={Binding IsEnabled}"/>
<Checkbox Checked="{Binding IsEnabled}"/>
</StackPanel>
</DataTemplate>
All of the XAML inside the DataTemplate element tells WPF what you want a single row to look like, within the control that is presenting your row model. That control will set the DataContext for the list item defined by the template, such that the {Binding...} declarations can reference your row model's properties directly by name.
That row model in turn might look something like this:
class MyRowModel : INotifyPropertyChanged
{
private string _text;
private bool _isEnabled;
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
OnPropertyChanged();
}
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (_isEnabled != value)
{
_isEnabled = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When your button to add a new item is clicked, don't mess with the UI directly. Instead, add a new element to your list of rows. Let WPF do the work of updating the UI to match.
NOTES:
The above uses StackPanel for the data template for convenience. If you want things lined up in columns, you'll probably want to use a Grid and declare its columns using SharedSizeGroup.
Or better yet, maybe you can use DataGrid which, assuming its default presentation of the values is acceptable to you, offers simple and automatic handling of exactly this type of layout.
The above is not meant to be anything close to a complete explanation of how to use data templating. It's just supposed to get you pointed in the right direction. Templating is one of WPF's more powerful features, but with that it also has the potential to be fairly complex.
For all of this to work, your types need to provide notification when they change. In the case of the row model, you can see it implements INotifyPropertyChanged. There is also an interface INotifyCollectionChanged; usually you don't have to implement this yourself, as WPF has the type ObservableCollection<T> which you can use just like List<T>, to store lists of data but with a way for notifications of changes to be reported to WPF.
I know this is a lot to take it all at once. Unfortunately, it's not feasible to try to explain all in a single answer all the things you need to learn to get this right. Frankly, even the above is pushing the limits as to what's within the scope of a Stack Overflow answer. But I hope that I've hit just the right highlights to get you looking at the right parts of the WPF documentation, and to understand the underlying philosophy of the WPF API.
i want to change textblock text in page initialize event
here is my xaml
<ListBox Margin="3,60,1,10" BorderThickness="2" Grid.Row="1" Name="lstAnnouncement" Tap="lstAnnouncement_Tap" Width="476" d:LayoutOverrides="VerticalMargin">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="thispanel" Grid.Row="1" Orientation="Horizontal" Height="120" Width="478" >
<StackPanel.Background>
<ImageBrush ImageSource="Images/Text-ALU.png" Stretch="Fill" />
</StackPanel.Background>
<Grid HorizontalAlignment="Left" Width="30" Margin="0,0,0,2" Background="#FF0195D5" Height="118">
<TextBlock x:Name="txtDate" TextWrapping="Wrap">
</TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
i want to change txtDate.Text using c# in code-behind but txtdate is not accessible in code behind so how to achieve it ?
The reason you're not able to access the txtDate object is because it's contained within the DataTemplate you're using for the ListBox. This isn't an error - the DataTemplate is being applied to every single item added to your ListBox.
Given that the ListBox creates, among other controls, a Grid containing a TextBlock with the name "txtDate", for every single item added to it, what would it mean to access the txtDate object? How would your program decide which of a (functionally) infinite number of txtDates associated with an identical number of ListBoxItems you meant when you referenced txtDate?
If you wanted to be able to easily change the content of txtDate, you'd want to bind the ItemsSource of your ListBox to a property in a ViewModel. The easiest way to do this would be to have that property be an IEnumerable containing a custom model type. This way, you could update the text property of that model and call NotifyPropertyChanged on the that property, and the UI would update to reflect the new data.
Here's an example:
public class YourViewModel
{
public List<YourModel> Models { get; set; }
}
public class YourModel : INotifyPropertyChanged
{
private string yourText;
public string YourText
{
get { return yourText; }
set
{
yourText = value;
NotifyPropertyChanged("YourText");
}
}
// add INotifyPropertyChanged implementation here
}
And then you'd want to bind the ItemsSource of the ListBox to YourViewModel's Models property, and the text of your TextBox to the YourModel's YourText property. Any time you change the YourModel.YourText property, it'll automatically update on the UI. I think it's probably subject to debate whether having your model implement INotifyPropertyChanged is proper MVVM, but I find it a lot easier in these cases than forcing the ViewModel to update every single model each time a change is made on one of them.
If you're not familiar with the MVVM pattern used with WPF, this might be a good start: MVVM example.
this function will help you... This will help u find the control inside of a listbox runtime..
public FrameworkElement SearchVisualTree(DependencyObject targetElement, string elementName)
{
FrameworkElement res = null;
var count = VisualTreeHelper.GetChildrenCount(targetElement);
if (count == 0)
return res;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(targetElement, i);
if ((child as FrameworkElement).Name == elementName)
{
res = child as FrameworkElement;
return res;
}
else
{
res = SearchVisualTree(child, elementName);
if (res != null)
return res;
}
}
return res;
}
Here first parameter is parent and the second parameter is the name of the element which in your case is "txtDate".. hope it works!!
As i am not very advanced in C# yet, I try to learn how to make my code more efficient.
I stored a lot of strings in some of the properties.
At the start of the application, i load all the seperatie properties into the textboxes.
I now ise this code to load them all:
private void LoadStoredStrings()
{
txtT1S1.Text = Properties.Settings.Default.strT1L1;
txtT1S2.Text = Properties.Settings.Default.strT1L2;
txtT1S3.Text = Properties.Settings.Default.strT1L3;
txtT1S4.Text = Properties.Settings.Default.strT1L4;
txtT1S5.Text = Properties.Settings.Default.strT1L5;
txtT1S6.Text = Properties.Settings.Default.strT1L6;
txtT1S7.Text = Properties.Settings.Default.strT1L7;
txtT1S8.Text = Properties.Settings.Default.strT1L8;
txtT1S9.Text = Properties.Settings.Default.strT1L9;
txtT1S10.Text = Properties.Settings.Default.strT1L10;
}
Obvious i can see the logic that each stored propertie ending with T1L1 also fits to the txt that ends with T1S1.
I just know this should be done in a more elegant and solid way than what i did now.
Could anyone push me in the right direction?
you can bind your properties directly to your textboxes
<UserControl xmlns:Properties="clr-namespace:MyProjectNamespace.Properties" >
<TextBox Text="{Binding Source={x:Static Properties:Settings.Default}, Path=strT1L1, Mode=TwoWay}" />
If you can get all of those constants into a List<string>, you could use it to bind to an ItemsControl with TextBlock inside:
Code behind or View Model
private ObservableCollection<string> _defaultProperties = new ObservableCollection<string>();
public ObservableCollection<string> DefaultProperties
{
get { return _defaultProperties; }
}
XAML
<ListBox ItemsSource="{Binding Path=DefaultProperties"}>
<ListBox.ItemTemplate>
<DataTemplate>
<!--Just saying "Binding" allows binding directly to the current data context vs. a property on the data context-->
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm having a problem with getting a string from a bound textblock within a listbox, when I use the code below, I can bind the listbox and the listbox has items showing up, but when the item in the list is clicked I don't get the proper string, I print a message box a message with objects names like
"MyApp.Item"
shows up instead. myApp is the name of the app and Item is the name of my model that I am binding to the listbox. The proper text from the selected item showed up when the listbox was not binded.
private void listBoxtrend_Tap(object sender, GestureEventArgs e)
{
selectedText = "";
selectedText = listBox.SelectedValue.ToString();
MessageBox.Show(selectedText);
}
xml
<ListBox ItemsSource="{Binding Item}" Foreground="RoyalBlue"
Height="395" HorizontalAlignment="Center"
Margin="12,111,0,0" Name="listBox"
VerticalAlignment="Top" Width="438"
TabIndex="10" Tap="listBox_Tap" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock TextWrapping="Wrap" FontSize="26" HorizontalAlignment="Left"
Name="tblItem" Text="{Binding ItemString}"
VerticalAlignment="Top" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd really appreciate if you could help me thanks
You're binding to the ItemString in the DataTemplate's TextBlock and the Item Collection in the ListView. As such the SelectedValue will be of the Item type. You should actually be doing something like this in your Tap handler to get at the ItemString's value...
private void listBoxtrend_Tap(object sender, GestureEventArgs e)
{
selectedText = "";
var selected = listBox.SelectedValue as Item;
selectedText = selected.ItemString;
MessageBox.Show(selectedText);
}
In your example, the ToString is printing the name of the class. You could also override ToString in your Item model to be whatever you want the string to be.
Note: the types and such may be a bit off, I guessed a bit based off of what you wrote in your question. Also, there is no need to set selectedText to an empty string that will just be overwritten in the third line above. I wanted to keep it so you could get some idea of what I changed in your code.
It's very simple, try following:
string selectedText = ListBox.GetItemText(ListBox.SelectedItem);
You need to also set the SelectedItem of the Listbox to something.
SelectedItem = {Binding SelectedItem}
and rename your ItemsSource to "Items" as that makes more sense.
Your SelectedItem in your codebehind or your ViewModel should then contain a property:
public class Item
{
public string ItemString { get;set; }
}
Try This...
string ListBoxConent = ((ListBoxItem)listbox.SelectedItem).Content.ToString();
Try
listBox.SelectedItem.ToString()
If a property isn't specified in ValueMember then SelectedValue returns the results of the ToString method of the object.
So I have a listbox that shows the subject of an email (I use the chilkat imap client) when I select the subject of an email I want to show the message body in a textbox but i cant figure out how to do it, obviusly i use the listbox selectindexchanaged event but how would i go about it
Code So Far
// Create an object, connect to the IMAP server, login,
// and select a mailbox.
Chilkat.Imap imap = new Chilkat.Imap();
imap.UnlockComponent("UnlockCode");
imap.Connect("Imap URL");
imap.Login("email address", "password");
imap.SelectMailbox("Inbox");
// Get a message set containing all the message IDs
// in the selected mailbox.
Chilkat.MessageSet msgSet;
msgSet = imap.Search("ALL", true);
// Fetch all the mail into a bundle object.
Chilkat.EmailBundle bundle = new Chilkat.EmailBundle();
bundle = imap.FetchBundle(msgSet);
// Loop over the bundle and display the From and Subject.
Chilkat.Email email;
int i;
for (i = 0; i < bundle.MessageCount - 1; i++)
{
email = bundle.GetEmail(i);
listBox1.Items.Add(email.From + ": " + email.Subject);
textBox1.Text = email.Body ;
}
// Save the email to an XML file
bundle.SaveXml("bundle.xml");
// Disconnect from the IMAP server.
// This example leaves the email on the IMAP server.
imap.Disconnect();
}
}
thanks in advance
Assuming that the email indexes stay the same (I think the safest way to make sure of that would be to cache the fetched bundle in the form), I'd change to using a ListView instead of the ListBox and then I'd add the indexes to the list, either as a separate column or in the Tag of the items.
After you'd set up the ListView to look as you need it to look (ListView.View = View.Details; and ListView.MultiSelect = false; are probably the main ones) instead of:
listBox1.Items.Add(email.From + ": " + email.Subject);
you could do something like (if you do it the Tag way, which is slightly easier but some people think is bad):
listView1.Items.Add(email.From + ": " + email.Subject).Tag = i;
And then when the user selects a subject in the list, as you say, you handle the ListView.SelectedIndexChanged event and then just do something like:
if(ListView.SelectedItems.Count > 0)
{
textBox1.Text = bundle.GetEmail((int)ListView.SelectedItems[0].Tag).Body;
}
Or if you're sure you only ever want to get out the text from the emails, you could insert the texts into the tags instead of the indexes.
In your xaml set up the listboxes to bind to the properties you'd like, and set up event handlers for when the selection changes.
<StackPanel>
<ListBox Name="listbox1" SelectionChanged="listbox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=From}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Name="listbox2" SelectionChanged="listbox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Subject}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Name="textbox1"></TextBox>
</StackPanel>
Then in your code behind. bind the listboxes to a list of the email objects.
listbox1.ItemsSource = emails;
listbox2.ItemsSource = emails;
finally you need to handle the event from the listboxes.
private void listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listbox = (ListBox)sender;
foreach (Email email in listbox.SelectedItems)
{
textbox1.Text = email.Body;
}
}
Please note this code is not tested.
Your issue is not with the email but with how you are displaying items in your form. You are trying to do things in a winforms way, which is fine for winforms (kind of) but really is pointless and code-heavy in WPF. You should do some reading about MVVM (plenty of questions here on the subject).
Here's a demo showing what you want to do using only a few lines of code that takes advantage of the binding infrastructure of WPF. You can create a new WPF app and copypaste the few lines (change my namespace and class names to match the app you create!) and see it in action.
There is one window. I'm simulating emails here; you'd get your emails and dump them in the collection:
public partial class MainWindow : Window
{
public ObservableCollection<FakeEmail> Emails { get; private set; }
public MainWindow()
{
Emails = new ObservableCollection<FakeEmail>();
// simulates emails being received; you would popoulate with valid emails IRL
Emails.Add(new FakeEmail
{ From = "herp", Subject = "derp", Message = "herp derp" });
Emails.Add(new FakeEmail
{ From = "foo", Subject = "bar", Message = "foo bar" });
Emails.Add(new FakeEmail
{ From = "Binding", Subject = "Rocks", Message = "Binding rocks" });
InitializeComponent();
}
}
/// <summary>
/// I don't have your libraries
/// </summary>
public sealed class FakeEmail
{
public string From { get; set; }
public string Subject { get; set; }
public string Message { get; set; }
}
I've added an ObservableCollection of type FakeEmail to the window. OCs work well with binds, as the collection notifies binds when elements are added or removed.
Next, the window. Please note, I'm not showing the <Window definition here, but I have named the window emailClient!
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox
x:Name="emailList"
ItemsSource="{Binding Emails, ElementName=emailClient}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock
Text="{Binding From}" />
<TextBlock
Text="{Binding Subject}"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock
Grid.Column="1"
Text="{Binding SelectedValue.Message, ElementName=emailList, FallbackValue='Select an email pls'}" />
</Grid>
Some of the finer notes: The ListBox's ItemsSource is bound to the ObservableCollection I defined on the window. The ListBox will listen for items coming and going in that collection, and use the DataTemplate to display UI for each item in the colleciton.
For each FakeEmail the ItemTemplate finds, it creates a new instance of the DataTemplate and contents, and sets the DataContext of the template to the FakeEmail instance. That means, within the DataTemplate I can simply bind against the properties of a FakeEmail instance and everything gets wired up at runtime.
The ListBox has a property called SelectedValue, which I can use to show the email message. When you select an item in the ListBox, SelectedValue is the instance from ItemsSource that is the DataContext of the DataTemplate; that which is currently displayed in that item in the UI. So, in order to show the currently selected email's message, I just need to bind against the ItemSource's SelectedValue's Message property, since SelectedValue will be the currently selected email.
And that's it. No listening, no "\r\n" BS. A couple binds and an Observable collection.