WPF: get my object index after cell value changed - wpf

So i have this Slider inside my ListView:
<DataTemplate x:Key="MyDataTemplate2">
<Grid Margin="-6" >
<Slider Name="sliderColumn" HorizontalAlignment="Left" VerticalAlignment="Center" TickPlacement="None"
Minimum="0" Maximum="50" Value="1" Style="{StaticResource SliderStyle}" Width="80"
TickFrequency="1" IsSnapToTickEnabled="True"/>
<TextBlock Text="{Binding Path=Value, ElementName=sliderColumn, StringFormat={}x{0}}" FontSize="11" Foreground="White"
VerticalAlignment="Center" HorizontalAlignment="Right"/>
</Grid>
</DataTemplate>
<GridViewColumn x:Name="SpeedCell" Width="100" Header="Speed" CellTemplate="{StaticResource MyDataTemplate2}" />
And after this Slider value changed i can get the new value:
private void sliderColumn_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
double val = e.NewValue;
}
But i am still missing the most important information i need: the index number of the object the it's Slider value changed.
The reason i need the index is because my ListView is full with my objects, this objects are inside RadObservableCollection and after this Slider value changed i want to set the new value base on the index number.
Any suggestions how to find this index ?

What you want to do it to write your value back in what ever object you store your data in. In your case you want to write it into a property of your list entries DataContext. It is really simple to do so. You just bind the Slider.Value to the property of your backing class.
Now I assume the class you bound your list entries to (so what ever class is on the ObservableCollection has a property SliderValue that stores a int. Like so:
public int SliderValue { get; set; }
So you bind your slider to the specific property in your backing class.
<Slider Value="{Binding Path=SliderValue}" />
I removed all formatting code from the slider tag to focus on the relevant bits you need to add.
EDIT: So regarding your last comment SliderValue would be Num.

Related

Handling GridItems click

Starting with a Grouped Items Page template, I want to be able to perform tasks on the grid items when they are clicked. Namely, I want to change the background image, and add/remove the underlying object to a list of selected items.
Here's my DataTemplate:
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="2" Margin="0,0,20,20">
<Grid HorizontalAlignment="Left" Width="390" Height="190">
<Grid.Background>
<ImageBrush ImageSource="/Assets/unselected.png" Stretch="None"/>
</Grid.Background>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Image VerticalAlignment="Top" Stretch="None" Source="{Binding ImageUrl}" Margin="10,10,0,0"/>
<StackPanel MaxWidth="270">
<TextBlock Text="{Binding Summary}"/>
<TextBlock Text="{Binding Brand}" />
<TextBlock Text="{Binding Detail}" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
OnTap, I want to togle the ImageSource value of the Grid.Background from unselected.png to selected.png. This I believe I can do using VisualStates and Storyboards, but I've been unable to get this to work in the past (I'll spare you the chaos of my attempts in xaml).
Needless to say, I've tried following the steps detailed here using Blend, but the Grid.Background property doesn't seems to be state specific. If I try changing the background brush in the Pressed or Selected states, it also changes for the Normal state.
Since I want to grab the data context of the selected item and add/remove it from a list, should I just be handling all this together in an OnTap event handler? I would prefer to keep these concerns separated, but I'll do what I need to...
thanks!
One clean way to do this would be engage the selection method (Tap) in such a way that it only opperates on its items, and the items themselves have properties which Implement the INotifyPropertyChanged interface
Your View Model would have a collection of your custom objects that have properties that can notify the ui
public class MyObject : INotifyPropertyChanged
{
private string _summary;
public string summary
{
get {return _summary}
set
{
_summary = value;
OnPropertyChanged()
}
}
//Other Properties: brand || detail
private ImageSource _backgroundImage;
public ImageSource backgroundImage
{
get {return _backgroundImage}
set
{
_backgroundImage = value;
OnPropertyChanged()
}
}
private bool _IsSelected;
public bool IsSelected
{
get {return _IsSelected;}
set
{
_IsSelected = value;
OnPropertyChanged()
}
}
}
Then although your code behind can be used to change the value of IsSelected, or Background image ... if you choose to go with IsSelected, you can still separate your concerns by not directly setting the resource of the background image in code behind. The Codebehind will only iterate over the items to toggle the IsSelected property, and you can use xaml to define the image that the background should use by creating a custom converter.
public class MyCustomControlOrPage.cs : UserControl //Or ApplicationPage
{
//.......code
protected void HandleTap(object sender, GestureEventArgs e)
{
foreach(var item in ((Listbox)sender).ItemsSource)
{
((MyObject)item.IsSelected = (MyObject)item.Name == (e.NewItems[0] as MyObject).Name? true: false;
}
}
}
then the converter
public class BackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ImageSource source = value == true ? new BitmapImage(uriForSelected) : new BitmapImage(uriForunselected);
return source;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
BitmapImage thisValue = value as BitmapImage;
return thisValue;
}
}
and FINALLY the XAML where the grid background binds to the IsSelected property and allows the converter to transform the bool to an ImageSource of type BitmapImage:
//add xmlns:Converters=clr-namesapce:Yournamespace.UpTo.TheNamespaceBackgroundConverterIsIn" to the page or control definition
<UserControl.Resources>
<Converters:BackgroundConverter x:key="BgSourceConverter" />
</UserControl.Resources>
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="2" Margin="0,0,20,20">
<Grid HorizontalAlignment="Left" Width="390" Height="190">
<Grid.Background>
<ImageBrush ImageSource="{Binding Path=IsSelected, Mode=TwoWay, Converter={Binding Source={StaticResource BGSourceConverter}}}" Stretch="None"/>
</Grid.Background>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Image VerticalAlignment="Top" Stretch="None" Source="{Binding ImageUrl}" Margin="10,10,0,0"/>
<StackPanel MaxWidth="270">
<TextBlock Text="{Binding Summary}"/>
<TextBlock Text="{Binding Brand}" />
<TextBlock Text="{Binding Detail}" TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>

WPF binding on Slider does not update on Maximum change

combobox1.SelectedItem is bound to SelectedItemProperty.
The when SelectedItemProperty is set, MaxValueProperty is calculated
MaxValueProperty is bound to slider1.Maximum
slider1.Value is bound to SliderValueProperty
These all work fine, except when combobox1.SelectedItem changes, and MaxValueProperty is calculated, and comes out less than SliderValueProperty.
In the View:
slider1.Maximum is updated, because MaxValueProperty was changed,
slider1.Value is set to slider1.Maximum as default behavior of a slider when the max changes to less than the value.
However, when this happens, SliderValueProperty does not get updated to the new slider1.Value
<Slider Name="slider1" Maximum="{Binding Path=MaxValueProperty, Mode=TwoWay}" Value="{Binding Path=SliderValueProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
I know that slider1.Value is getting changed, because I have a label bound to it, and the label changes
<Label Content="{Binding ElementName=slider, Path=Value, Converter={StaticResource NumberToStringConverter}}" />
How can I ensure the binding is updated?
Rather than relying on the default behavior of the slider and a TwoWay binding, raise a PropertyChanged event after you compute the new maximum in your SelectedItemProperty setter, first on "SliderValueProperty" using the new maximum, and then second on "MaxValueProperty". Do your computations on your private backing fields, not on the public properties.
This will cause the system to adjust first the value of the slider, and then the maxvalue, and it should move the slider position for you.
Set the binding mode on slider1's Maximum to OneWay, as well; I think that will do the trick.
As follows with a viewmodel like this:
Class viewmodel
Implements INotifyPropertyChanged
Private _selectedItemProperty As ComboBoxItem
Public Property SelectedItemProperty As ComboBoxItem
Get
Return _selectedItemProperty
End Get
Set(value As ComboBoxItem)
MaxValueProperty = value.Content
If _sliderValueProperty > _maxValueProperty Then _sliderValueProperty = _maxValueProperty
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("MaxValueProperty"))
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("SliderValueProperty"))
End Set
End Property
Private _maxValueProperty, _sliderValueProperty as Single
Public Property MaxValueProperty As Single ' with setter that will raise PropertyChanged and use the backing field
'...
Public Property SliderValueProperty As Single ' with a setter that will raise PropertyChanged and use the backing field
'...
End Class
And the XAML looks a little like this:
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="40,38,0,0" Name="ComboBox1" VerticalAlignment="Top" Width="120" SelectedItem="{Binding SelectedItemProperty}">
<ComboBoxItem Content="50" />
<ComboBoxItem Content="30" />
<ComboBoxItem Content="10" />
</ComboBox>
<Slider Name="slider1" Height="23" HorizontalAlignment="Left" Margin="87,135,0,0" VerticalAlignment="Top" Width="100" Value="{Binding SliderValueProperty}" Minimum="1" Maximum="{Binding MaxValueProperty}" />
<Label Content="{Binding ElementName=slider1, Path=Value}" Margin="40,174,293,96" />
</Grid>

Solution for filtering all instances of an object?

I'm wondering if I can do something like this with CollectionViewSource too. I have a DataTemplate that looks like this:
<DataTemplate DataType="{x:Type local:MyObject}">
<StackPanel Orientation="Horizontal">
<Grid>
<Image Source="Images\gear16.png" />
<Image Source="Images\disk.gif" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Visibility="{Binding MyProp, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
<TextBlock Margin="5,0,0,0" Text="{Binding Name}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
So of course, everything bound to that type of object takes that DataTemplate, or in other words, every object of type MyObject gets that datasource. Can I do something similar for CollectionViewSource? Make every object of type MyObject go through the filtering methods?
The problem is that I have several instances of this collection oF MyObject, and it will be very difficult to filter one by one (I think), and still handle updates to data and everything, so I'm wondering if there is a solution like this.
Thanks!
You can use CollectionView.Filter property to perform filtering. There's no way for any "group" filtering, only "one by one" as you say. You can read here about filtering.
Your filtering handler will look like this:
private void ShowOnlyBargainsFilter(object sender, FilterEventArgs e)
{
if (e.Item is MyObject)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
Hope it helps.

Silverlight: Define event handler in hierarchical data template

I am having problems getting at a click event of a button and am using Silverlight 3.0 w/ matching Silverlight Toolkit.
Problem
I have this TreeView:
TreeView sample http://a.imagehost.org/0939/TreeView.png.
The value for a certain node is the sum of the values of its children. Only in leaves can data be added (for the time being).
What I want to achieve is that a user can add (and eventually remove) entries in the tree to eventually create a custom diagram.
To that end I would like the "plus sign" to insert a new line / node as child of the node where the user clicked. (I.e. if I click the plus at "Display", I get a line below to specify CRT or TFT or whatever.)
Thing is, for all my brain is worth, I don't know how to receive any useful event.
The TextBlock, TextBox and Button are defined in a hierarchical template and I can't define a Click-handler in that template.
OTOH, I haven't found a way to get at the template items of a certain TreeViewItem from within (c#) code. Very well am I able to do trv.ItemContainerGenerator.GetContainerFromItem(item), and as Justin Angel showed I can very well change the font size, but didn't find any way to access the textbox or button.
Is there any way to capture the click event to the button? Or any alternative way of getting something that gives the "add below" functionality?
Thank you in advance.
More Data
The treeview xaml is this:
<controls:TreeView x:Name="SankeyDataTree"
ItemTemplate="{StaticResource SankeyTreeTemplate}" BorderThickness="0"
Background="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top">
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Loading..."/>
</DataTemplate>
</controls:TreeViewItem.HeaderTemplate>
</controls:TreeViewItem>
</controls:TreeView>
I use this HierarchicalDataTemplate (and stole the appraoch from Timmy Kokke):
<Data:HierarchicalDataTemplate x:Key="SankeyTreeTemplate" ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<!-- ... -->
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Value.name, Mode=TwoWay}" VerticalAlignment="Center"/>
<TextBox Text="{Binding Path=Value.flow, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{box\}}"/>
<TextBlock Text="{Binding Path=Value.throughput, Mode=TwoWay}" Margin="4,0" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.Column="1" TextAlignment="Right" Visibility="{Binding Children, Converter={StaticResource BoxConverter}, ConverterParameter=\{block\}}"/>
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</Grid>
</Data:HierarchicalDataTemplate>
To this TreeView is bound a "SimpleTree", whose Values hold basically onle a string (name) and two doubles (flow and throughput).
public String name { get; set; }
public Double flow { get; set; }
public Double throughput { get; set; }
(Plus the code for the INotifyPropertyChanged to get a twoway bind to the text boxes.)
You can attach a Behavior to the Button in the HierarchicalDataTemplate and let that handle Click events from the Button.
Download and install the Expression Blend 3 SDK. Add a reference to System.Windows.Interactivity in the project and add a Behavior attached to a Button:
public class ButtonClickBehavior : Behavior<Button> {
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.Click += ButtonClick;
}
protected override void OnDetaching() {
base.OnDetaching();
AssociatedObject.Click -= ButtonClick;
}
void ButtonClick(object sender, RoutedEventArgs e) {
Node node = AssociatedObject.DataContext as Node;
if (node != null) {
// Button clicked. Do something to associated node.
}
}
}
Attach the Behavior to the Button in the HierarchicalDataTemplate (assuming this namespace declaration: xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"):
<Button Margin="0" Grid.Column="2" Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<interactivity:Interaction.Behaviors>
<local:ButtonClickBehavior/>
</interactivity:Interaction.Behaviors>
</Button>
If desired you can add properties to ButtonClickBehavior and set those from XAML to create a more reusable Behavior.
You can handle the button click event in the code behind. To get to the data, just bind it to the Tag attribute.
<Button Margin="0" Grid.Column="2"
Click="Button_Click" Tag="{Binding}"
Style="{StaticResource TreeViewItemButtonStyle}">
<Image Source="../Assets/add.png" Margin="0"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
In the code behind, handle it and access the element.
private void Button_Click(object sender, RoutedEventArgs e)
{
var data = ((Button)sender).Tag as SimpleTreeNode
}
Where SimpleTreeNode is the name of your tree element class.
You should be able to add a new node to the data found now.

Looking for a WPF ComboBox with checkboxes

My google skills fail me. Anyone heard of a control like that for WPF. I am trying to make it look like this (winforms screenshot).
You can do this yourself by setting the DataTemplate of the combo box. This article shows you how - for a listbox, but the principle is the same.
Another article here is perhaps a better fit for what you are trying to do, simple set the first column of the item template to be a checkbox and bind it to a bool on your business object.
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"
Width="20" />
<TextBlock Text="{Binding DayOfWeek}"
Width="100" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
There is my combobox. I use Martin Harris code and code from this link Can a WPF ComboBox display alternative text when its selection is null?
<ComboBox Name="cbObjects" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="2,2,6,0" SelectionChanged="OnCbObjectsSelectionChanged" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}" Width="20" VerticalAlignment="Center" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<TextBlock Text="{Binding ObjectData}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock IsHitTestVisible="False" Name="tbObjects" Text="Выберите объекты..." Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" Margin="6,2,6,0" />
Small class for datasource:
public class SelectableObject <T> {
public bool IsSelected { get; set; }
public T ObjectData { get; set; }
public SelectableObject(T objectData) {
ObjectData = objectData;
}
public SelectableObject(T objectData, bool isSelected) {
IsSelected = isSelected;
ObjectData = objectData;
}
}
And there is two handler - one for handling CheckBox clicked and one for forming Text for ComboBox.
private void OnCbObjectCheckBoxChecked(object sender, RoutedEventArgs e) {
StringBuilder sb = new StringBuilder();
foreach (SelectableObject<tblObject> cbObject in cbObjects.Items)
{
if (cbObject.IsSelected)
sb.AppendFormat("{0}, ", cbObject.ObjectData.Description);
}
tbObjects.Text = sb.ToString().Trim().TrimEnd(',');
}
private void OnCbObjectsSelectionChanged(object sender, SelectionChangedEventArgs e) {
ComboBox comboBox = (ComboBox)sender;
comboBox.SelectedItem = null;
}
For ComboBox.ItemsSource I use
ObservableCollection<SelectableObject<tblObject>>
where tblObject is type of my object, a list of which I want to display in ComboBox.
I hope this code is useful to someone!
Give a try to CheckComboBox from Extended WPF Toolkit.
The main advantage for me is having two lists for binding:
all items available for selection
just selected items
I find this approach more practical. In addition you can specify value and display members of the collections you're binding.
If you don't want to bring a bunch of other controls with CheckComboBox, you can get the source code of it, it's pretty straightforward (need to bring Selector class as well).
ComboBox with Checkboxes
<ComboBox Height="16" Width="15">
<CheckBox Content="First Checkbox" />
<CheckBox Content="Second Checkbox" />
<CheckBox Content="Third Checkbox" />
<TextBlock Text="Some Text" />
</ComboBox>
The provided answers surprisingly didn't work for me, I tried many variations and kept getting error messages about the checkbox not being part of combobox and the data context seemed to be broken.
In the end I didn't have to do anything involving data templates or any code behind and my bindings are working fine (not shown in example)
I must say I'm happy with how easy this turned out to be after reading all the answers.

Resources