Create pick list from different table in combobox wpf ef6 - wpf

Right up front, I am a newbie. I am creating a window with many comboboxes functioning as pick lists. I have code behind xaml and auto generated files in a wpf ef6 application. I have a table (InspectionTypes) with InspectionsID (Int) and InspectionName (String) properties. In my Estimates window, I have a combobox that I desire to list and view/select an InspectionTypeName and store the InspectionsID in the record.
Here is my XAML portion for the combobox:
{ <ComboBox x:Name="inspectionType1ComboBox"
Grid.Column="2" Grid.Row="1" Height="Auto" Width="200" Margin="3"
HorizontalAlignment="Left" VerticalAlignment="Center"
ItemsSource="{Binding}"
DisplayMemberPath="InspectionType1">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
}
I have been attempting to use a LINQ query and an ObservableCollection. Currently, I am receiving the exception: System.FormatException: 'Input string was not in a correct format.'
This exception was originally thrown at this call stack:
System.Number.StringToNumber(string, System.Globalization.NumberStyles, ref System.Number.NumberBuffer, System.Globalization.NumberFormatInfo, bool)
System.Number.ParseInt32(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
string.System.IConvertible.ToInt32(System.IFormatProvider)
System.Convert.ChangeType(object, System.Type, System.IFormatProvider)
MS.Internal.Data.SystemConvertConverter.Convert(object, System.Type, object, System.Globalization.CultureInfo)
MS.Internal.Data.DynamicValueConverter.Convert(object, System.Type, object, System.Globalization.CultureInfo)
System.Windows.Controls.Primitives.Selector.VerifyEqual(object, System.Type, object, MS.Internal.Data.DynamicValueConverter)
System.Windows.Controls.Primitives.Selector.FindItemWithValue(object, out int)
System.Windows.Controls.Primitives.Selector.SelectItemWithValue(object, bool)
System.Windows.Controls.Primitives.Selector.OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs)
code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
System.Windows.Data.CollectionViewSource estimatesViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("estimatesViewSource")));
System.Windows.Data.CollectionViewSource inspectionTypesViewSource =
((System.Windows.Data.CollectionViewSource)
(this.FindResource("inspectionTypesViewSource")));
context.Estimates.Load();
estimatesViewSource.Source = context.Estimates.Local;
inspectionTypesViewSource.Source = context.InspectionTypes.Local;
FillComboBoxes();
}
private void FillComboBoxes()
{
MIDatabase01Entities1 db = new MIDatabase01Entities1();
//var ItemList = (from d in db.InspectionTypes select new { d.InspectionTypesID,
d.InspectionTypeName }).ToList();
var observablelist = new ObservableCollection<InspectionType>(db.InspectionTypes);
inspectionType1ComboBox.DisplayMemberPath = "InspectionTypeName";
inspectionType1ComboBox.SelectedValuePath = "InspectionTypesID";
inspectionType1ComboBox.SelectedValue = "InspectionTypesID";
//inspectionType1ComboBox.ItemsSource = ItemList;
inspectionType1ComboBox.ItemsSource = observablelist;
}
I have been trying to find an answer for weeks without success. Could you please assist?

Do not set the combobox from xaml.cs using ItemsSource in the method. You can make a property for the same and bind it in xaml. This issue is coming because you are assigning data before the view is rendered.
public partial class MainWindow : Window
{
public ObservableCollection<InspectionType> observablelist { get; set;
public MainWindow()
{
InitializeComponent();
System.Windows.Data.CollectionViewSource cvs = ((System.Windows.Data.CollectionViewSource)(this.FindResource("cvs")));
DataContext = this;
FillComboBoxes();
}
private void FillComboBoxes()
{
observablelist = new ObservableCollection<InspectionType>();
observablelist.Add(new InspectionType()
{
InspectionTypeName = "ABC",
InspectionTypesID = 1
});
}
}
xaml.cs
<Window x:Class="WpfApp1.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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:dat="clr-
namespace:System.Windows.Data;assembly=PresentationFramework"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="500"
Loaded="Window_Loaded">
<Window.Resources>
<CollectionViewSource Source="{Binding observablelist}"
x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="InspectionTypeName" />
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<dat:PropertyGroupDescription PropertyName="InspectionTypesID" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="50" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Source={StaticResource cvs}}"
DisplayMemberPath="InspectionTypeName"
Name="lb">
<ListBox.GroupStyle>
<x:Static Member="GroupStyle.Default" />
</ListBox.GroupStyle>
</ListBox>
<ComboBox Grid.Row="2"
Height="100"
AllowDrop="True"
Width="100"
DisplayMemberPath="InspectionTypeName"
SelectedValuePath="InspectionTypesID"
ItemsSource="{Binding observablelist}"></ComboBox>
</Grid>
</Window>

Related

WPF Core 3.1 bound TextBox not visible at runtime

Learning WPF Core 3.1 WPF MVVM pattern, using Visual Studio 2019 4.8.04084.
I have a MainWindow with the following (boilerplate excluded)
<Window>
<Grid
Width="1024"
Height="768"
Margin="0,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<usercontrols:CustomerMaintenanceControl
x:Name="CustomerMaintenance"
Grid.Row="0"
Height="109"
VerticalAlignment="Top" />
</Grid>
</Window>
The CustomerMaintenanceControl user control looks like this:
<UserControl
x:Class="Redacted.UserControls.CustomerMaintenanceControl"
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:usercontrols="clr-namespace:Redacted.UserControls"
xmlns:vm="clr-namespace:Redacted.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<UserControl.Resources>
<vm:CustomerMaintenanceViewModel x:Key="viewModel" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<usercontrols:CustomerMaintenanceGridControl
x:Name="gridControl"
Grid.Row="0"
Height="200"
DataContext="{StaticResource viewModel}" />
<usercontrols:CustomerMaintenanceDetailControl
x:Name="detailControl"
Grid.Row="1"
Width="800"
Height="200"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DataContext="{StaticResource viewModel}" />
</Grid>
</UserControl>
Here's the CustomerMaintenanceDetailControl, where the issue is:
<UserControl
x:Class="Redacted.UserControls.CustomerMaintenanceDetailControl"
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:local="clr-namespace:Redacted"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid Margin="0,0,0,325">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TabControl
x:Name="tabControl"
Grid.Row="0"
Margin="0,0,0,0">
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock>General</TextBlock>
</StackPanel>
</TabItem.Header>
<StackPanel>
<TextBox
x:Name="companynmTextbox"
Width="322"
Margin="0,32,228,0"
HorizontalAlignment="Left"
IsReadOnly="True"
Text="{Binding Path=Entity.Companynm}" />
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</UserControl>
So the CustomerMaintenanceControl has two user controls in it, a CustomerMaintenanceGridControl that has a DataGrid and then a CustomerMaintenanceDetailControl which has a tab control and a bound TextBox. The grid is pulling data in fine, so the model and data layer are working. However the 'companyNmTextBox' control on CustomerMaintenanceGridControl is displaying nothing.
There are no binding errors in VS. In addition, when I run the application and open the Live Visual Tree, I can open the properties for 'companyNmTextBox' and under Inherited\DataContext I have my 'Customers' ObservableCollection there and populated, and my 'Entity' property on the model populated with the data for the first Customer. Yet nothing showing onscreen.
Since people are voting to close because of 'debugging details' (?) - the debugging details are in the last paragraph - there are no binding or errors in Visual Studio, and everything appears bound OK in the Live Visual Tree/ The 'desired behaviour' is that the textbox displays the value of the thing that it is bound to. If there are further debugging steps I could investigate I'd be delighted to hear them.
Your Entity has to implement INotifyPropertyChanged interface
public class Entity : INotifyPropertyChanged
{
private string companynm;
public string Companynm
{
get
{
return companynm;
}
set
{
this.companynm = value;
NotifyPropertyChanged();
}
}
...
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also TextBox companynmTextbox has the property IsReadOnly set to true, so in that case I would modify the binding.
Text="{Binding Path=Entity.Companynm, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
It was a typo to the call to RaisePropertyChanged() in the ViewModel the CustomerMaintenanceDetailControl is using.
I had:
private Customer _entity = new Customer();
public Customer Entity
{
get { return _entity; }
set
{
_entity = value;
RaisePropertyChanged("Customer"); // <- problem here
}
}
I should have had:
private Customer _entity = new Customer();
public Customer Entity
{
get { return _entity; }
set
{
_entity = value;
RaisePropertyChanged("Entity"); // <- doh
}
}

WPF multiple Instance of ViewModel in Usercontrol and Window

This is my first question on stackoverflow.
I have a Usercontrol that contains a viewModel as a staticresource. Than I have a Window that contains this UserModel. I want to send commands from the Window to the viewmodel that is used by the usercontrol. So I bound the Viewmodel class to the Usercontrol and to the Window as a static Resource. But the viewmodell ist instanciated 2 times. Once from the Window and once from the usercontrol. What am I doing wrong?
Here is the xaml of the usercontrol:
<UserControl x:Class="ScanVM.ScanView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:t="clr-namespace:ScanVM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="501.305">
<UserControl.Resources>
<t:ScanVM x:Key="ABC"/>
</UserControl.Resources>
<DockPanel DataContext="{Binding Source={StaticResource ABC}}">
<Grid ShowGridLines="True" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="10*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Label Content="Geräte:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" Margin="0,10,10,0"/>
<DataGrid ItemsSource="{Binding Path=ScanIDs, Mode=OneWay}" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" Grid.Row="1" VerticalAlignment="Top" AutoGenerateColumns="false" Height="152" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ScanIds}" ClipboardContentBinding="{x:Null}" Header="Bezeichnung" MinWidth="200"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</DockPanel>
This is the code of my window:
<Window x:Class="ScanViewer.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:ribbon="clr-namespace:System.Windows.Controls.Ribbon;assembly=System.Windows.Controls.Ribbon"
xmlns:self="clr-namespace:ScanViewer"
xmlns:t="clr-namespace:ScanVM;assembly=ScanVM"
mc:Ignorable="d"
Title="Scan-Viewer" Height="682.225" Width="694">
<Window.Resources>
<t:ScanVM x:Key="ABC"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding
Command="self:MainWindow.ExitCommand"
CanExecute="ExitCommand_CanExecute"
Executed="ExitCommand_Execute"/>
</Window.CommandBindings>
<DockPanel>
<ribbon:Ribbon DockPanel.Dock="Top">
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu Visibility="Collapsed">
<RibbonApplicationMenuItem Header="Beenden"/>
<RibbonApplicationMenu.FooterPaneContent>
<RibbonButton Label="Exit"/>
</RibbonApplicationMenu.FooterPaneContent>
</RibbonApplicationMenu>
</Ribbon.ApplicationMenu>
<RibbonTab Header="Start">
<RibbonGroup Header="Programm">
<RibbonButton Label="Beenden" Margin="0,5,0,0" Command="self:MainWindow.ExitCommand" />
</RibbonGroup>
<RibbonGroup Header="Scannen">
<RibbonButton Label="Daten Scannen" Margin="0,5,0,0" Command="{Binding ScanCmd, Source={StaticResource ABC}}"/>
</RibbonGroup>
</RibbonTab>
</ribbon:Ribbon>
<Grid DockPanel.Dock="Top" Height="464">
<t:ScanView DataContext="{Binding Source={StaticResource ABC}}"/>
</Grid>
</DockPanel>
And this is the code of my Viewmodel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Windows.Input;
using System.Threading;
using MVVMBase;
namespace ScanVM
{
public class ScanVM : ViewModelBase
{
private ICommand _doWorkCmd;
private ICommand _scanCmd;
private bool scanAllowed = false;
public List<int> ScanIDs
{
get;
set;
}
public ScanVM()
{
_doWorkCmd = new DelegateCommand((param) => DoWork());
_scanCmd = new DelegateCommand((param) => Scan());
}
public bool NWScanAllowed(object executeAllowed)
{
return scanAllowed;
}
public ICommand WorkCmd
{
get { return _doWorkCmd; }
}
public ICommand ScanCmd
{
get
{
return _scanCmd;
}
}
private void Scan()
{
scanAllowed = true;
}
private void DoWork()
{
}
}
}
The answer depends on what you want to do with the shared object.
You can either move ABC to App.xaml inside <Application.Resources> tag to share it throughout your project.
Or you can remove the one in user control and use the ABC in window's resources.
You will need a valid reference path to address the ABC in window resource from user control in this case.
which brings us to these two situations:
The valid path is possible to find:
So let's say if your user control is a part of the mentioned window (and if the window's DataContext is set to ABC) then inside the user control you will reference it like this:
<DockPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}">
The valid path is impossible to find and you just want the static resource. If you insist on doing it this way it is better to just move the resource to App.xaml than to try to tweak this out.

Combobox with images items-WPF

I'm newbie on WPF and I would like to add images items to combobox which will read from hard disk according filter on it's name.
filter on images name should be binds to text property of different element.
Any advice?
Thanks!
It is a good example to show MVVM approach. Code below will do the job:
XAML
<Window x:Class="simplest.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:simplest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBox x:Name="outputFolder" Height="30" Margin="5" Grid.Column="0" Text="{Binding Filter}"/>
<ComboBox Height="30" Grid.Column="1" Margin="5" ItemsSource="{Binding Images}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image MaxWidth="64" MaxHeight="64" Source="{Binding}" />
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
C#
class MainWindowViewModel : INotifyPropertyChanged
{
private string _filter;
private ObservableCollection<string> _images = new ObservableCollection<string>();
private readonly string _filesSearchPath = "d:\\";
public MainWindowViewModel()
{
Filter = "*.jpg";
}
public string Filter
{
get
{
return _filter;
}
set
{
_filter = value;
OnPropertyChanged();
RefreshImagesCollection();
}
}
public ObservableCollection<string> Images
{
get
{
return _images;
}
}
private void RefreshImagesCollection()
{
_images.Clear();
foreach (var fileName in Directory.GetFiles(_filesSearchPath, _filter))
{
_images.Add(fileName);
}
}
private void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
ItemTemplate and ItemSource are the properties you need to set.
ItemTemplate should point to Datatemplate and ItemSource to Collection of strings(path to images).
This link will help you.

Link the heights of the rows of two listview

I have a custom control that has two listViews employing GridViewRowPresenters. I need the row heights of the two listViews to be the same. The catch is I wont know what the heights will be until run time.
That is, both will have their heights set to Auto and when the control is rendered one listView will have its rows at ActualHeight = 30 the other at ActualHeight = 40. I want them both to be 40.
If the ListViews use a shared item template, all you would have to do is wrap your GridViewRowPresenter in a grid and then use a SharedSizeGroup on that grid's only row. Here's how it can be achieved.
XAML
<UserControl x:Class="WpfApplication1.UserControl2"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<GridViewColumnCollection x:Key="GridViewColumns">
<GridViewColumn Width="100"
DisplayMemberBinding="{Binding Path=Text}"
Header="Text" />
</GridViewColumnCollection>
<DataTemplate x:Key="RowTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="RowSizeGroup" />
</Grid.RowDefinitions>
<Border BorderBrush="Black" BorderThickness="1">
<GridViewRowPresenter Columns="{StaticResource GridViewColumns}" />
</Border>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<GridViewHeaderRowPresenter Columns="{StaticResource GridViewColumns}" />
<ListView Grid.Row="1"
ItemTemplate="{StaticResource RowTemplate}"
ItemsSource="{Binding Path=Items1}" />
<ListView Grid.Row="2"
ItemTemplate="{StaticResource RowTemplate}"
ItemsSource="{Binding Path=Items2}" />
</Grid>
</UserControl>
Code-Behind (for reference, but not really important)
using System.Collections.ObjectModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for UserControl2.xaml
/// </summary>
public partial class UserControl2
{
public UserControl2()
{
InitializeComponent();
DataContext = this;
Items1 = new ObservableCollection<object>
{
new Item {Text = "Hello World!"},
new Item {Text = "Hello \nWorld!"}
};
Items2 = new ObservableCollection<object>
{
new Item {Text = "Testing 1\n2\n3"}
};
}
private class Item
{
public string Text { get; set; }
}
public ObservableCollection<object> Items1 { get; private set; }
public ObservableCollection<object> Items2 { get; private set; }
}
}
If you want to set the height of your rows explicitly, just replace <RowDefinition Height="Auto" SharedSizeGroup="RowSizeGroup" /> with <RowDefinition Height="40" SharedSizeGroup="RowSizeGroup" /> where 40 in this case is the specific height.

Accessing methods of a control that is in a Content Template

Relative WPF new-comer, this will probably have a simple solution (I hope!). I have a class with two properties:
public class MyClass
{
public String Name { get; set; }
public String Description { get; set; }
}
I have a user control which has a textblock and a button: the textblock displays text (obviously) and the button is used to either bold or unbold the text of the text block:
MyControl.xaml:
<UserControl
x:Class="WpfApplication1.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<TextBlock
x:Name="txtDescription"
Grid.Column="0"
Text="{Binding Path=Description, RelativeSource={RelativeSource AncestorType={x:Type this:MyControl}}}" />
<Button
x:Name="btnBold"
Grid.Column="1"
Content="Bold"
Click="btnBold_Click" />
</Grid>
</UserControl>
MyControl.xaml.cs:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(MyControl));
public String Description
{
get { return GetValue(MyControl.DescriptionProperty) as String; }
set { SetValue(MyControl.DescriptionProperty, value); }
}
public MyControl()
{
InitializeComponent();
}
private void btnBold_Click(object sender, RoutedEventArgs e)
{
ToggleBold();
}
public void ToggleBold()
{
if (txtDescription.FontWeight == FontWeights.Bold)
{
btnBold.Content = "Bold";
txtDescription.FontWeight = FontWeights.Normal;
}
else
{
btnBold.Content = "Unbold";
txtDescription.FontWeight = FontWeights.Bold;
}
}
}
In my MainWindow I have a tab control which has an item template (to display MyClass.Name in the header of each tab) and a content template. The content template contains one of my above controls and MyClass.Description is bound to MyControl.Description:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<TabControl x:Name="tabItems">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
Description="{Binding Description}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<MyClass> myClasses = new List<MyClass>();
myClasses.Add(new MyClass() { Name = "My Name", Description = "My Description" });
myClasses.Add(new MyClass() { Name = "Your Name", Description = "Your Description" });
tabItems.ItemsSource = myClasses;
}
}
When the program runs I add two objects of type MyClass to a List, set the list to the ItemsSource property of the tab control and it all works perfectly: I get two tabs with "My Name" and "Your Name" as the headers, the description is shown in the correct place and the button turns the bold on or off correctly.
My question is this, how do I add a button OUTSIDE of the tab control which could call the MyControl.ToggleBold method of the MyControl object which is in the content template of the selected item:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="tabItems" Grid.Row="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
x:Name="myControl"
Description="{Binding Description}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Grid.Row="1" Content="Toggle Selected Tab" Click="Button_Click" />
</Grid>
</Window>
MainWindow.xaml.cs:
...
private void Button_Click(object sender, RoutedEventArgs e)
{
MyClass myClass = tabItems.SelectedItem as MyClass;
MyControl myControl;
///get the instance of myControl that is contained
///in the content template of tabItems for the
///myClass item
myControl.ToggleBold();
}
...
I know I can access the data template by calling tabItems.SelectedContentTemplate but as far as I can tell I cannot access controls within the template (and I don't think I should be doing that either). There is the FindName method but I don't know pass as the templatedParent parameter.
Any help would be hugely appreciated.
You can navigate the VisualTree to find the control you're looking for.
For example, I use a set of custom VisualTreeHelpers which would allow me to call something like this:
var myControl = VisualTreeHelpers.FindChild<MyControl>(myTabControl);
if (myControl != null)
myControl.ToggleBold();

Resources