This is my first time building a UWP app and I'm new to c#/Windows in general. I am trying to use a Pivot in the UI. I want the pivot headers to be from an ObservableCollection of usb devices connected, which go by the class Device_Info. Each USB Device has a property called HardwareRevNumMajor that I would like to display as each pivots header. My xaml looks like this:
<Page
x:Class="usb_test.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:usb_test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:code="using:usb_test"
mc:Ignorable="d">
<Page.DataContext>
<local:View_Model/>
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Pivot x:Name="pivot1" Title="Pivot" Opacity="0.99" ItemsSource="{Binding Devices}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<Binding Path="Device_Info.HardwareRevNumMajor"/>
</TextBlock.Text>
</TextBlock>
<!--<TextBlock Text="{Binding HardwareRevNumMajor}">
</TextBlock>-->
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding DeviceFiles}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding numBlocks}"/>
<TextBlock Text="{Binding syncTime}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
</Grid>
As soon as a Device_Info object gets added to the Devices observableCollection I get an error. Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)). Then if I click in my MainPage.xaml file in the designer I see this:
TargetException: Object does not match target type.
StackTrace:
at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj,
BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
InnerException: None
You can see from the xaml above that I've tried a number of ways to display the HarwareRevNumMajor property that lives on a Device_Info by the commented out TextBlock code.
My ViewModel looks like this:
namespace usb_test
{
public class View_Model
{
public ObservableCollection<Device_Info> _devices;
public ObservableCollection<File_Info> _deviceFiles;
public CoreDispatcher _dispatcher;
public View_Model() {
_devices = new ObservableCollection<Device_Info>();
_deviceFiles = new ObservableCollection<File_Info>();
}
public ObservableCollection<Device_Info> Devices
{
get
{
return _devices;
}
}
public ObservableCollection<File_Info> DeviceFiles
{
get
{
return _deviceFiles;
}
}
In My MainPage.xaml.cs I have this:
namespace usb_test
{
public sealed partial class MainPage : Page
{
Device_List devList = new Device_List();
Device_Structure deviceStruct = new Device_Structure();
View_Model viewModel = new View_Model();
public MainPage()
{
this.InitializeComponent();
viewModel._dispatcher = Dispatcher;
this.DataContext = viewModel;
devList.devices.CollectionChanged += this.OnCollectionChanged;
deviceStruct.deviceFiles.CollectionChanged += this.OnCollectionChanged;
...
This line of code is what adds a device to the list:
await viewModel._dispatcher.RunAsync(CoreDispatcherPriority.Normal, async() => { devList.devices.Add(devInfo); });
This line succeeds and adds a Dev_Info object into the devices observableCollection and shortly after the application crashes.
I'm sure there are more errors when I try to display File Info stuff later in the xaml but I'd really appreciate just getting the pivot header to display correctly. Like I said I'm very new to this so I'm assuming there are a number of problems, I'd appreciate any advice/clarity into what is stopping me from not being able to display a property from a Dev_Info object.
Thanks!
Your items from Devices are of type Device_Info. In your DataTemplate your bind the text property to Device_Info.HardwareRevNumMajor. Since the context is your item (Device_Info), you try to access a property Device_Info.
If your write:
<TextBlock Text="{Binding HardwareRevNumMajor}" />
You try to access the property HardwareRevNumMajor wich probably exists in Device_Info.
Thanks for the suggestions above. I made some progress.
I rewrote my xaml like this:
<Page
x:Class="usb_test.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:usb_test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:code="using:usb_test"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Pivot x:Name="pivot1" Title="Pivot" Opacity="0.99" ItemsSource="{x:Bind viewModel.Devices}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Device_Info">
<TextBlock Text="{x:Bind HardwareRevNumMajor}"></TextBlock>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate x:DataType="local:Device_Info">
<ListView ItemsSource="{x:Bind devStruct.deviceFiles}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:File_Info">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Red" Text="{x:Bind fileName}" HorizontalAlignment="Center" Margin="0,0,20,0"/>
<TextBlock Foreground="Red" Text="{x:Bind numBlocks}" HorizontalAlignment="Center" Margin="0,0,20,0"/>
<Button Content="Download File" Click="{x:Bind onDownloadClick}" HorizontalAlignment="Center" Margin="0,0,20,0">
</Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
</Grid>
You'll notice I took out the
<Page.DataContext>
<local:View_Model/>
</Page.DataContext>
from my previous xaml code.
Then I used ItemsSource="{x:Bind viewModel.Devices}"> in the Pivot. viewModel is defined in my MainPage.xaml.cs and is an instance of the View_Model class I created above. Then from there I added a DataType to DataTemplate which is a Device_Info and then I could easily access an attribute of the Device_Info object, in this case HardwareRevNumMajor.
The PivotItemTemplate is a little more confusing. The class Device_Info has a property on it called devStruct which is a class I created (Device_Struct). An instance of Device_Struct has an ObservableCollection of File_Info objects. So this collection tells us the files on the device and it's called deviceFiles. That is the collection I wanted to use inside the Pivot Items. I'm not sure if there's a way to do this without inserting a <ListView> into the XAML but this is what I found works. With this I can now display information about the files on each Pivot Item.
I also switched from using Binding to x:Binding. To be honest I'm not sure exactly why that helped but when I used Binding I was getting: Object reference not set to an instance of an object. If anyone has a better understanding of that, I'd love to hear about it. Thanks again!
Related
In my project, I wanted a UserControl that can display different formats of an image (bitmap, svg), selected from a ListBox. The SelectedItem of the ListBox is bound to the appropriate view model, which in turn changes the DataContext of the UserControl, and what I want to achieve is for it to change the displaying control (an Image for bitmaps, a SharpVectors.SvgViewBox for svg files) through data templates. It does so, but raises data binding errors, as if the templates were still intact whilst the UserControl's DataContext has already been changed.
I should like to a) avoid any data binding errors even if they cause no visible problems b) understand what is happening, so I prepared a MWE, which, to my surprise, displays the same behaviour, so I can present it here.
My minimal UserControl is as follows:
<UserControl x:Class="BindingDataTemplateMWE.VersatileControl"
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:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding Content.Length, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate DataType="{x:Type system:DateTime}">
<TextBlock Text="{Binding Content.DayOfWeek, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl
Content="{Binding .}" />
</Grid>
</UserControl>
The MainWindow that references this UserControl has the following XAML:
<Window x:Class="BindingDataTemplateMWE.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:BindingDataTemplateMWE"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
SelectedItem="{Binding Selected}"
ItemsSource="{Binding Items}" />
<local:VersatileControl
Grid.Column="1"
DataContext="{Binding Selected}" />
</Grid>
</Window>
with the following code-behind (to make the MWE indeed minimal, I made the window its own DataContext, but originally there is a dedicated view model):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace BindingDataTemplateMWE
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object selected;
public event PropertyChangedEventHandler PropertyChanged;
public List<object> Items { get; }
public object Selected {
get { return selected; }
set {
selected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Selected)));
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items = new List<object>() { "a string", DateTime.Now, "another string" };
}
}
}
When I select a different item in the list, the desired effect takes place: the UserControl displays the length if a string is selected and the day of week when a DateTime. Still, I get the following binding error when selecting a DateTime after a string:
Length property not found on object of type DateTime.
and conversely, selecting a string after a DateTime yields
DayOfWeek property not found on object of type String.
It is clear that what I am doing is not meant to be done, but I do not know what the correct paradigm is and what happens in the background. Please advise me. Thank you.
I've seen this problem often when creating complex data templates (several levels of nesting) when views are loaded/unloaded. Honestly, some of such errors I am ignoring completely.
In your case something similar happens because you are manipulating DataContext directly. At the moment the new value is set, the previous value is still used in bindings, which monitor for source change and will try to update the target.
In your scenario you don't need this constant monitoring, so an easy fix is to use BindingMode.OneTime:
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding Content.Length, RelativeSource={RelativeSource AncestorType=ContentControl}, Mode=OneTime}" />
</DataTemplate>
Is it possible to add and bind user controls dynamically? Maybe I'll show sample code to show what I exactly mean.
MainWindow:
<UniformGrid
Rows="11"
Columns="11"
DataContext="{StaticResource vm}">
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[0].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[0].SomeData, Mode=OneWay}" />
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[1].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[1].SomeData, Mode=OneWay}" />
<local:DynamicUserControl
ButClickControl="{Binding Path=UserControlObjects[2].ButClickCommand}"
SomeDataInUserControl="{Binding Path=UserControlObjects[2].SomeData, Mode=OneWay}" />
.....
</UniformGrid>
In ViewModel there is an array of UserControlObjects. But in this array I will have over 100 elements, so it is not the best option to write all elements one by one. Is there any way to add DynamicUserControls not in XAML but somewhere in code in loop with keeping the MVVM pattern and binding?
Use an ItemsControl with the UniformGrid as ItemsPanel and the DynamicUserControl in the ItemTemplate:
<ItemsControl DataContext="{StaticResource vm}"
ItemsSource="{Binding UserControlObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="11" Columns="11"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:DynamicUserControl
ButClickControl="{Binding ButClickCommand}"
SomeDataInUserControl="{Binding SomeData}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my opinion, you would want to keep any controls out of your view model. You could however keep the individual view models that back the controls in a list within the main view model. For example, create the view model that will provide the data for the “dynamic” controls.
class SubViewModel
{
public string Name { get; private set; } = string.Empty;
public SubViewModel(string aName)
{
Name = aName;
}
}
And in the main view model you can do whatever you would do to dynamically create instances. In this case, I am just creating then in a for loop.
class MainWindowViewModel
{
public ObservableCollection<SubViewModel> SubViewModels
{
get
{
return mSubViewModels;
}
} private ObservableCollection<SubViewModel> mSubViewModels = new ObservableCollection<SubViewModel>();
public MainWindowViewModel()
{
for(int i = 0; i < 30; i++)
{
SubViewModels.Add(new SubViewModel($"Control: {i}"));
}
}
}
Then in the view, you can utilize an ItemsControl with an UniformGrid based ItemsPanelTemplate, and then whatever you want for the data template, whether you define it there explicitly, or make a user control (like your local:DynamicUserControl) to clean things up. In this sample, the data template it explicitly defined.
<Window x:Class="ListOfViewsSample.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:ListOfViewsSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding SubViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="LightGray" Margin="10">
<Label Content="{Binding Name}" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
which results in the following:
If don’t want the multiple dynamic views to be the same, you can look into data template selectors to display something different based on the specified view model, but based in your question I think you were looking for a list of the same control/data. Hope this helps!
The usual way of doing this is:
Create an ItemsControl for the dynamic items you want to create
Override the ItemsPanel to whatever you need (UniformGrid in your case)
Bind it to a list of view models, with one view model per control
Define DataTemplates to map each view model type to its corresponding view type
I'm trying to create a video player that can play multiple videos at once. I've gotten players working and now I'm trying to allow a user to add additional videos at run time. I'm displaying videos in a uniform grid, which uses a converter to decide how many rows and columns it should generate based on the count of videos. It works fine when you define how many players there are before running, however when I add a player while its running the uniform grid doesn't update the rows or columns. It simply adds another video to whatever structure it had before. Is there anyway that I can force it to reevaluate the rows/columns?
TL;DR: Can I reevaluate the rows and columns of a uniform grid while viewing it? How ?
The xaml for the control is below.
Additional Infomation That Might be helpful:
1. Players is an ObservableCollection
2. The Control is being presented with a TabControl
<UserControl x:Class="Views.AllVideos"
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:local="clr-Views"
mc:Ignorable="d">
<ItemsControl ItemsSource="{Binding Players}"
x:Name="AllVideosControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding Players, Converter={StaticResource CountToColumns}, Mode=OneWay}"
Rows="{Binding Players, Converter={StaticResource CountToRows}, Mode=OneWay}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="1"
Margin="5">
<ls:PanelPreview DataContext="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
Setting Rows and Columns isn't needed at all. Everything will work without them too. UniformGrid will place everything uniformly inside whatever container.
If still you want to use Binding , then following works for me , Cols are fixed here. Converters are not need.
void Players_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Rows = (int)Math.Ceiling(Players.Count / Cols);
}
The problem appears to be that nothing is notifying the binding that the number of items in Players has changed: The Binding is sitting there waiting for your viewmodel to tell it there's a new Players collection.
But since Players is an ObservableCollection, it'll raise PropertyChanged when it's Count changes. So, simplest case:
<UniformGrid
Rows="{Binding Players.Count}"
...
/>
The following works for me to update the row and column counts as items are added to Players. The arithmetic in the converter could use a little work, but the point is that the bindings update UniformGrid.Rows and UniformGrid.Columns every time Players changes. This works because ObservableCollection raises PropertyChanged("Count") every time an item is added or removed. Because I'm binding to a property of an ObservableCollection, the binding subscribes to the ObservableCollection's PropertyChanged event, so it knows to update when Count changes.
C#
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
(DataContext as ViewModel).Players.Add($"Player {(DataContext as ViewModel).Players.Count + 1}");
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
if ((DataContext as ViewModel).Players.Count > 0)
(DataContext as ViewModel).Players.RemoveAt(0);
}
}
public class ViewModel
{
private ObservableCollection<String> _players = new ObservableCollection<string>();
public ObservableCollection<String> Players
{
get { return _players; }
}
}
public class RowsColumnsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Math.Ceiling(Math.Sqrt((int)value));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ItemsControl
Grid.Row="1"
ItemsSource="{Binding Players}"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid
Rows="{Binding Players.Count, Converter={StaticResource RowsColumns}}"
Columns="{Binding Players.Count, Converter={StaticResource RowsColumns}}"
/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button
Content="Add Player"
Click="AddButton_Click"
Margin="2"
Width="100"
/>
<Button
Content="Remove Player"
Click="RemoveButton_Click"
Margin="2"
Width="100"
/>
</StackPanel>
</Grid>
I have a combo box that is not working as I expect at runtime. I can use the mouse to expand the drop-down window, but clicking an item does not seem to select it. The dropdown goes away, but the selection is not changed. The same control seems to work as expected using the keyboard. Arrow up/down changes the selection. I can use the arrow keys to choose and enter to select to change the value as well.
How do I get clicking to select an item?
<DataTemplate DataType="{x:Type myType}">
<Border ...>
<Grid x:Name="upperLayout">
<Grid x:Name="lowerLayout">
<ComboBox x:Name="combo"
Grid.Column="2"
ItemsSource="{Binding Things}"
SelectedItem="{Binding SelectedThing}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</Border>
</DataTemplate>
I can't really tell what's wrong from your code however, I'd strongly suggest you to use Snoop to debug your controls (http://snoopwpf.codeplex.com/)
By holding Ctrl+Shift and pointing the mouse where you ComboBox is supposed to grab the input you would instantly find out who is having the focus instead of your combo box.
You can even change the value of a property, really your best friend for debugging your templates !
EDIT
I'm afraid but the code you've posted works for me:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication6="clr-namespace:WpfApplication6"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type wpfApplication6:MyType}">
<Border>
<Grid x:Name="upperLayout">
<Grid x:Name="lowerLayout">
<ComboBox x:Name="combo"
Grid.Column="0"
ItemsSource="{Binding Path=Things}"
SelectedItem="{Binding Path=SelectedThing}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type wpfApplication6:MyThing}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid x:Name="grid">
<ContentControl x:Name="content" ContentTemplate="{StaticResource myTemplate}" Margin="58,79,71,40" />
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MyType type = new MyType()
{
Things = new List<MyThing>() {new MyThing() {Name = "aaa"}, new MyThing() {Name = "bbb"}}
};
content.Content = type;
}
}
public class MyType
{
public MyThing SelectedThing { get; set; }
public List<MyThing> Things { get; set; }
}
public class MyThing
{
public string Name { get; set; }
}
Maybe something else is screwing it such as a style with no key or whatever, post more of your code you're having a problem with.
Root cause was that another developer had implemented some code that changed the focus on the preview mouse down event. This code was updated to have the desired behavior without modifying focus and the combo box now works as expected. The information needed to diagnose was not in the original question (can't publish it all...).
I have a combobox which I want to call a method from MainViewModel but it binds to EmployeesOverviewViewModel. Is it possible to do this? if yes - how?
Here is my code for the combobox
<ComboBox ScrollViewer.CanContentScroll="False" Text="Select Employees" DataContext="{Binding EmployeesOverviewViewModel, Source={StaticResource ViewModelLocator}}" Name="employeeComboBox" ItemsSource="{Binding Employees}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected}" Content="{Binding Path=Name}" Width="{Binding ElementName=employeeComboBox, Path=ActualWidth}" VerticalAlignment="Center">
</CheckBox>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I have thought about using Command but I couldn't figure out the binding problem.
BR
if your MainViewModel is anywhere is the visual tree as the DataContext, then you can achieve what you want with RelativeSource in your CommandBinding for your ComboBox.
In expanding on my comment to the original post. Shown below is an example of how to bind to the data context of the parent.
Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.SomeCommand}"
Set the path to command on the viewmodel you want to bind to.
Do both ViewModels know each other? Then the EmployeesOverviewViewModel could provide an delegate that you would execute and the MainWindowViewModel could use this delegate to "bind" it to its method. (edit: it would be enough if the MainWindowViewModel knows the EmployeesOverviewViewModel)
Otherwise you could try to use a binding using FindAncestor and try to get the MainWindowView. The problem then would be that you don't just need the view itself but its DataContext.
I would say you have two realistic approaches.
1) if your view can see a suitable instance of MainViewModel, you should be able to bind to a command or whatever you need on that by using StaticResource or DynamicResource. I see you're using a StaticResource to find the viewmodel providing the ComboBox's items, would something similar work for finding MainViewModel?
2) if your view can't see a MainViewModel, but your viewmodel can, get the viewmodel to expose a suitable command or method and just have that call MainViewModel's version. This is cleaner, as then your EmployeesOverviewView doesn't have to know anything about MainViewModel at all.
I'd prefer option 2.
You can do that using a MultiValueConverter. I show you a small sample.
I have a window with 3 CheckBox:
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="300" Width="400" mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:WpfApplication7="clr-namespace:WpfApplication7" d:DesignHeight="371"
d:DesignWidth="578" SizeToContent="WidthAndHeight">
<Window.Resources>
<WpfApplication7:MultiBooleanConverter x:Key="multiBooleanConverter" />
</Window.Resources> <Grid>
<CheckBox Content="Hallo">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource ResourceKey=multiBooleanConverter}">
<Binding ElementName="checkBox1" Path="IsChecked"/>
<Binding ElementName="checkBox2" Path="IsChecked"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
<CheckBox x:Name="checkBox1" Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="64,72,0,0" VerticalAlignment="Top" />
<CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="62,120,0,0" x:Name="checkBox2" VerticalAlignment="Top" />
</Grid>
</Window>
The MultiValueConverter is declared like that:
public class MultiBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Cast<bool>().Any(b => b);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
object[] returnValue = new object[targetTypes.Length];
for (int i = 0; i < targetTypes.Length; i++)
{
returnValue[i] = (bool)value;
}
return returnValue;
}
}