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
Related
I am using a ListView element in my XAML:
<ListView
x:Name="myList"
IsItemClickEnabled="true"
ItemClick="onDrawerItemClick"
SelectionMode="Single"
ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemTemplate>
<DataTemplate>
<Grid
Width="260">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="44" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<Image
x:Name="image"
Source="{Binding myIcon}"
Grid.Column="0" />
<TextBlock
Text="{Binding myTxt}"
Grid.Column="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and I populate it using Bind property like so:
List<MyObj> listData = a list with title + image uri;
myList.ItemsSource = listData;
I need to disable click only for some items depending on some value from MyObj in my list but the others to have it. In Android we use adapter for that, how should I handle it here?
First, you should create a new bool property called Disabled inside your MyObj object.
Then, subscribe to myList's ContainerContentChanging event where you have access to the ListViewItem and its corresponding Item, which in this case is your MyObj. So, if MyObj.Disabled is true, make that ListViewItem non-clickable.
private void myList_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
var listViewItem = args.ItemContainer;
if (listViewItem != null)
{
var model = (MyObj)args.Item;
if (model.Disabled)
{
listViewItem.IsHitTestVisible = false;
// OR
//listViewItem.IsEnabled = false;
}
}
}
Keep in mind that you might want to use listViewItem.IsEnabled = false if you want that item to appear dimmed. This is because the default ListViewItemstyle has a Disabled state that reduces its Opacity; while setting listViewItem.IsHitTestVisible = false won't change its appearance in any way.
The listView is a strange control as it does not have any mechanism to disable selection.
So what I suggest you do is rather handle the event that notifies the framework that an item has been selected by attaching an event handler to ItemSelectionChanged and in there perform a deselect on the item:
yourListView.ItemSelectionChanged += yourListView_ItemSelectionChanged;
private void yourListView_ItemSelectionChanged(
object sender,
ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
e.Item.Selected = false;
}
Please let me know if my answer helps :)
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.
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.
I have a ListBox in app, it has an image and textbox inside. I want to set 2 colors and 3rd one for selected item.
<ListBox.ItemTemplate>
<DataTemplate x:Name="Template1">
<StackPanel Orientation="Horizontal" >
<Image Width="100" Height="100" Source="{Binding SmallImage}"></Image>
<Grid>
<TextBlock Text="{Binding Caption}" Foreground="{Binding txtColor}"></TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
when I'm changing the foreground color, then the selected item doesn't highlights (I kept it by default).
I tried to add an event to ListBox,
private void DList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBoxItem selectedItem = DList.SelectedItem as ListBoxItem;
selectedItem.Foreground = new SolidColorBrush(Colors.Red);
}
but it shows an exception:
NullReferenceException
"Use the "new" keyword to create an object instance"
If you're going to handle the SelectionChanged event, then you might as well use the SelectionChangedEventArgs object:
private void DList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedDataObject = e.AddedItems[0]; // assuming single selection
ListBoxItem selectedItem =
ListBoxName.ItemContainerGenerator.ContainerFromItem(selectedDataObject);
selectedItem.Foreground = new SolidColorBrush(Colors.Red);
}
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.