WPF Tooltip data binding - c#

I'm trying to create tool tip for ListView rows.
Current data structure used to generate listview:
public class Map
{
public string Name{get;set;}
public List<Difficulty> Difficulties{get;set;}
}
public class Difficulty
{
public int ID{get;set;}
public string Name{get;set;}
}
// Items to list are added like.
ListView.Items.Add(new Map(){....});
This is what I have with tool tip XAML code:
<Setter Property="ToolTip">
<Setter.Value>
<ItemsControl ItemsSource="{Binding Difficulties}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}" />
<TextBlock Grid.Column="1" Text="{Binding ID}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Setter.Value>
</Setter>
It all works, but what I need is to display Name from Map, not Difficulty.

Try
Text="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ListViewItem}}"

Related

WPF Enable Highlight, Copy, and Paste inside a Listbox

I have a list of strings that I want to display on a menu. I used a Listbox and it works just that it won't let me highlight or copy/paste.
Here is my XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500"/>
<ColumnDefinition Width="500"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="450"/>
<RowDefinition Height="318"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="1" Grid.Column="1" x:Name="uiOCRData" />
</Grid>
Heres what I have in C#
List<string> lines = new List<string>();
uiOCRData.ItemsSource = lines;
Thanks for the help!
You must use a ListBox.ItemTemplate so that you can include a control inside your ListBox.
Since you want to be able to select text etc., the best option is to use a TextBox.
<ListBox Grid.Row="0" Name="uiOCRData">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT
Let's say you want to bind to a list of some class objects instead of a simple list of strings. Say your class looks like this:
public class Data
{
public int Id { get; set; }
public string Name { get; set; }
}
Then you can bind to any one of chosen Properties of the class like this:
<ListBox Grid.Row="0" Name="uiOCRData">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="100" Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Updating TabControl ItemSource ViewModels when specific state changes

I have a TabControl bound to an ObservableCollection property of view models, TabViewModelsCollection.
When another property gets set from within the view models, DeviceState, I'd like to raise a property changed event and tell my TabControls ItemSource to refresh.
The problem is my ItemSource is an ObservableCollection of ViewModels and when I call RaisePropertyChanged("TabViewModelsCollection"); nothing gets updated.
Furthermore my tab views contain multiple user controls and bindings.
The scenario that is supposed to play out is: A device is located on the network, data is collected, and the tabs of device information should then be updated.
Currently my TabControl only updates when I select a different device then select the device whos information I want to see. Think left panel list of devices, right panel with tabs of device info.
Let me know what part of the code you guys might want to see, my codebase is quite large so it would be hard to post it.
Here is where the TabControl is defined in my view:
<!-- Devist List Controls -->
<Grid DockPanel.Dock="Left" Margin="5,5">
<local:DeviceListView DataContext="{Binding DeviceListViewModel}" Grid.Row="0"/>
</Grid>
<GroupBox Header="Device Information" DockPanel.Dock="Right" Margin="0,0,5,5">
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection}" SelectedItem="{Binding DeviceListViewModel.SelectedDevice.SelectedTabItemVm}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type vms:HomeViewModel}">
<local:HomeTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:ConfigurationViewModel}">
<Grid>
<local:ConfigurationFileView Visibility="{Binding Configuration, TargetNullValue=Collapsed, FallbackValue=Visible}"/>
<local:ErrorTab Visibility="{Binding Path= Configuration, TargetNullValue=Visible, FallbackValue=Hidden}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:ExpansionModulesViewModelFactory}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<DockPanel >
<local:ExpansionModulesList Title="Discovered/Enumerated"
DataContext="{Binding DiscoveredModules}"
/>
<GridSplitter Width="5"/>
<local:ExpansionModulesList Title="User Action Required"
DataContext="{Binding FaultyModules}"
/>
</DockPanel>
</StackPanel>
<DockPanel Grid.Row="1">
<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft" Margin="5" IsEnabled="{Binding IsCommandEnabled}">
<Button Content="Cancel" HorizontalAlignment="Right"
Command="{Binding CancelExpansionCommand }"
ToolTip="Revert all local modifications by refreshing data from the controller." />
<Separator Width="10"/>
<Button Content="Apply" HorizontalAlignment="Center"
Command="{Binding ApplyExpansionCommand }"
ToolTip="Apply all changes to the controller." />
<Separator/>
</StackPanel>
</DockPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:LogViewModel}">
<local:LogView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:SignalStrengthViewModel}">
<local:SignalStrengthView />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.ItemContainerStyle>
EDIT: I want to be able to call raised property changed on this and have it refresh all of my tab views..
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection}" SelectedItem="{Binding DeviceListViewModel.SelectedDevice.SelectedTabItemVm}" >
RaisePropertyChanged("TabViewModelsCollection");
Hi please try the next solution:
VM code redaction
private State _deviceState;
private ObservableCollection<object> _tabViewModelsCollection;
public State DeviceState
{
get { return _deviceState; }
set
{
_deviceState = value;
RaisePropertyChanged("DeviceState");
UpdateTabViewModelsCollection();
}
}
public ObservableCollection<object> TabViewModelsCollection
{
get
{
return _tabViewModelsCollection ??
(_tabViewModelsCollection = new ObservableCollection<object>(GetDeviceData()));
}
}
private void UpdateTabViewModelsCollection()
{
_tabViewModelsCollection = null;
RaisePropertyChanged("TabViewModelsCollection");
}
private List<object> GetDeviceData()
{
//implement here the data collection process
throw new NotImplementedException();
}
Xaml redaction (define the UpdateSourceTrigger)
ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection, UpdateSourceTrigger=PropertyChanged}"
Let me know if it was helpful.
Regards.

Nested databinding with WPF and C#

I'm trying to make a budget program. Where I need to have groupboxes with a list of textblocks inside.
<ItemsControl DataContext="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding}">
<ItemsControl DataContext="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I need somehow to databind a list (perhaps?) with groupboxes so I'd create a list of group boxes, with some lines inside that would be a text with a currency value. So that I could create a group called "Apartment", with two lines "Rent $3000" and "Maintenance $150". Then I could have a second group called "Car" with lines "Insurance", "Loan" and "Maintenance" for instance.
But how would I databind this? And how would I need in C# to perform this. I'm at a loss.
Building off of Jay's comment, you would want to create a Hierarchical data model. Note I have left implementing INotifyPropertyChanged on the properties to you
public class BudgetLineItem : INotifyPropertyChanged
{
public string Name { get; set; }
public decimal Cost { get; set; }
}
public class BudgetGroup : INotifyPropertyChanged
{
public string GroupName { get; set; }
public ObservableCollection<BudgetLineItem> LineItems { get; set; }
}
public class BudgetViewModel : INotifyPropertyChanged
{
public ObservableCollection<BudgetGroup> BudgetGroups { get; set; }
}
Then your data-template would look like this:
<ItemsControl DataContext="{Binding ViewModel}"
ItemsSource="{Binding BudgetGroups}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding GroupName}">
<ItemsControl ItemsSource="{Binding LineItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Cost}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I could be off base here, but it sounds like you want to change the DataTemplate based on the type of object that is being bound from a list of heterogeneous objects.
If that's the case, you want to look into DataTemplateSelectors or create DataTemplates for each of the types you want to support in the list.
For example, for an Apartment you might have:
<DataTemplate DataType="local:ApartmentBudget">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
a Car may look like:
<DataTemplate DataType="local:CarBudget">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Insurance}" />
<TextBlock Text="{Binding Loan}" />
<TextBlock Text="{Binding Maintenance}" />
</StackPanel>
</DataTemplate>
Then your ItemsControl can be set like:
<ItemsControl ItemSource="{Binding BudgetItems}">
The correct DataTemplate will be picked based on the data type. You can have even more control by creating a custom DataTemplateSelector.
See https://msdn.microsoft.com/en-us/library/ms742521(v=vs.100).aspx for more information.

Building a list of options with MVVM

I have a ObservableCollection> property that I would like to display in a list box in a WPF window using the MVVM model. Ideally I would like to have a listbox that has a check box for the bool and a label for the string and when the user changes the value of the checkbox it would change the corresponding bool. What is the best way to go about doing this?
Try using an ItemsControl, and don't bind to a list of checkboxes, define a model:
<ItemsControl ItemsSource="{Binding YourModelList}">
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Selected}"/>
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl>
In the ViewModel:
public ObservbleCollection<YourModel> YourModelList { get; set; }
And the Model:
public class YourModel
{
public bool Selected {get;set;}
public string Description {get;set;}
public int ID {get;set;}
}
Implement INotifyPropertyChanged as necessary
Idealy, if you want to follow the MVVM approach, everyone of your elements in the ObservableCollection should be a ViewModel.
These viewModels should expose 2 properties, such as string Description and bool IsSelected.
All you need then is to provider your ListBox with a Style in order to display a checkbox and textblock for each databound ViewModel.
The following XAML implements such a Style. Note: The usercontrol DataContext should hold a ViewModel containing an ObservableCollection<YourClass> Items { get; set; } property where YourClass exposes string Description { get; set; } and bool IsSelected { get; set; }. You will obviously want to throw in some INotifyPropertyChanged magic in there.
<Grid>
<Grid.Resources>
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="itemChoix" Margin="5,5,0,0" IsChecked="{Binding IsSelected, Mode=TwoWay}" IsEnabled="{Binding IsEnabled, Mode=TwoWay}" />
<TextBlock Margin="5,5,0,0" Text="{Binding Description, Mode=TwoWay}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="0" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" Style="{StaticResource BoxBorder}" Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid>
<ListBox ItemsSource="{Binding Path=Items}" SelectionMode="Multiple" Style="{StaticResource CheckBoxListStyle}"/>
</Grid>
</Border>
</Grid>
Create your public ObservableCollection property in your ViewModel
Create an IsSelected bool property.
Populate collection from constructor or command.
Create a command for the checkbox that sets the IsSelected property.
Do the Xaml binding

WPF - Change a style in code behind

I have a list box that displays the results of a TFS Query. I want to change the style of the ListBoxItem in the code behind to have the columns that are included in the query results.
The style for the ListBoxItem is defined in my Windows.Resoruces Section. I have tried this:
public T GetQueryResultsElement<T>(string name) where T : DependencyObject
{
ListBoxItem myListBoxItem =
(ListBoxItem)(lstQueryResults.ItemContainerGenerator.ContainerFromIndex(0));
// Getting the ContentPresenter of myListBoxItem
ContentPresenter myContentPresenter =
myListBoxItem.Template.LoadContent().FindVisualChild<ContentPresenter>();
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate; <------+
T myControl = (T)myDataTemplate.FindName(name, myContentPresenter); |
|
return (T)myControl; |
} |
|
ContentTemplate is null ----------------------------------------------+
But the ContentTemplate is null. I got that code from here, then modified it with the LoadContent call (the orginal code gave null for the ContentPresenter).
Anyway. If you know a way to change an existing style in the code behind I would love to see it.
Specifics if you want them:
I am going for WrapPanel in my ListBoxItem Style. This is what I want to add the extra TextBlock items to.
Here is part of my style:
<!--Checkbox ListBox-->
<Style x:Key="CheckBoxListStyle" TargetType="ListBox">
<Style.Resources>
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Tag" Value="{Binding Id}"/>
<Setter Property="Background">
<Setter.Value>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderThickness="1" BorderBrush="#D4D4FF">
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}, Path=ActualWidth}" ScrollViewer.CanContentScroll="True" Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Grid.Background>
<Binding Path="Type" Converter="{StaticResource WorkItemTypeToColorConverter}" />
</Grid.Background>
<CheckBox VerticalAlignment="Center" Grid.Column="0" IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}" Name="chkIsSelected" />
<WrapPanel Grid.Column="1" Margin="5,0,5,0" Name="QueryColumns">
<TextBlock VerticalAlignment="Center" Text="{Binding Id}" Name="txtID" />
<TextBlock VerticalAlignment="Center" Margin="5,0,5,0" Text="{Binding Title}" Name="txtTitle" />
</WrapPanel>
You're going against the grain here, trying to manipulate visual elements directly in code-behind. There's a much simple solution involving data binding.
I'll provide the general solution because I don't know the specifics of your solution.
Once you get your query results, create an enumeration that returns a column name, and a field value for each iteration.
Example:
class NameValuePair
{
public string Name { get; set; }
public object Value { get; set; }
}
public IEnumerable<IEnumerable<NameValuePair>> EnumerateResultSet(DataTable resultSet)
{
foreach (DataRow row in resultSet.Rows)
yield return EnumerateColumns(resultSet, row);
}
public IEnumerable<NameValuePair> EnumerateColumns(DataTable resultSet, DataRow row)
{
foreach (DataColumn column in resultSet.Columns)
yield return new NameValuePair
{ Name = column.ColumnName, Value = row[column] };
}
And in your code-behind, once you get your DataTable result set, do this:
myResultsList.ItemsSource = EnumerateResultSet(myDataTable);
The XAML might look like this:
<Window.Resources>
<DataTemplate x:Key="ColumnTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="2" Padding="2">
<WrapPanel>
<TextBlock Text="{Binding Name}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Value}" Margin="0,0,10,0"/>
</WrapPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="RowTemplate">
<Grid>
<ItemsControl
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ColumnTemplate}"
Margin="0,5,0,5"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Name="myResultsList" ItemTemplate="{StaticResource RowTemplate}"/>
</Grid>
Sample output:

Categories

Resources