Binding to a COM Object - Cannot resolve a Property - c#

I am currently running into some trouble with binding objects to a WPF ListBox. The number of Elements is populated correctly but the Name property cannot be accessed. I get the following error message:
System.Windows.Data Error: 40 : BindingExpression path error: 'Name' property not found on 'object' ''__ComObject' (HashCode=17252513)'.
BindingExpression:Path=Name; DataItem='__ComObject'
(HashCode=17252513); target element is 'TextBlock' (Name=''); target
property is 'Text' (type 'String')
However, I am able to print this property correctly to the console at runtime.
It looks like the object is not casted correctly
XAML:
<ListBox Name="lbSelectConfiguration" Margin="10" ItemsSource="{Binding Configurations}">
<ListBox.ItemTemplate>
<StackPanel Margin="5" Orientation="Horizontal">
<Image Source="/Resources/Configuration.png" Margin="0,0,8,0" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind
this.DataContext = viewModel;
this.viewModel = viewModel;
foreach (Configuration config in this.viewModel.Configurations)
{
Console.WriteLine(config.Name);
}
Can you help me out? Thank you.

Because __ComObject is mentioned in the error, I am presuming that the collection of objects you are binding to, are actually COM objects that implement the "Configuration" interface, rather than CLR ones.
(whether that COM object is actually implemented using C# (with ComVisible(true)) or C++ is immaterial, it's the fact you are using an "interface" - am presuming it is coming from an "COM interop library" i.e. after you did an add reference to a COM library?).
You can try this:
<TextBlock Text="{Binding (mynamespacewithinterface:Configuration.Name)}" />
Otherwise you can look at these links to understand the difficulty in the Binding system resolving bindings (COM interfaces and NET interfaces each have their own quirks/limitations), and some workarounds:
https://social.msdn.microsoft.com/Forums/en-US/69389bb5-ba90-4a22-8a75-e1a21f1d3a16/comobject-in-datatemplate?forum=wpf
http://badecho.com/2012/07/adding-interface-support-to-datatemplates/
https://social.msdn.microsoft.com/Forums/vstudio/en-US/1e774a24-0deb-4acd-a719-32abd847041d/data-templates-and-interfaces
WPF databinding to interface and not actual object - casting possible?
https://social.msdn.microsoft.com/Forums/vstudio/en-US/1e774a24-0deb-4acd-a719-32abd847041d/data-templates-and-interfaces?forum=wpf
http://blog.pmunin.com/2012/01/xaml-datatemplates-binding-to-interface.html
http://leecampbell.blogspot.co.uk/2008/09/generic-binding-in-wpf-through-explicit.html
http://www.dev102.com/2008/04/23/wpf-datatemplate-for-interfaces-not-supported/

As far as I can remember, ComObjects are dynamic in .NET, so the compiler will never recognise the names of the object's properties at compile time. If that's really important for you, then just create a .NET wrapper object that implements the INotifyPropertyChanged interface and copy all of the object's properties to your WPF object. The compiler will then obviously be able to read the names of the properties.

Related

Binding works fine, but intellisense says: Cannot resolve property XXX in data context of type 'object'

I have a binding to a parent-element. How can I provide the data type for the DataContext in the binding, so intellisense can resolve the bound Properties?
The binding works fine at runtime. So, I have the following XAML structure:
<TabControl Name="TabDynamic"
ItemsSource="{Binding TabItems, Mode=OneWay}" ...>
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Command="{Binding ElementName=TabDynamic, Path=DataContext.DeleteTabCommand}"
CommandParameter="{Binding ElementName=TabDynamic, Path=DataContext.TabItems/}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="viewModels:ConnectionInfoVM">
<views:BufferViewerControl/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
The data type of the DataContext is "viewModels:ConnectionInfoVM".
Intellisense will now underline both Properties on the DataContext (so DeleteTabCommand and TabItems are underlined).
I already tried to use the design-time data-context definition "d:DataContext" within the Button element like so:
d:DataContext="{d:DesignInstance viewModels:ConnectionInfosVM}"
But this does not change the intellisense warnings.
I also tried to define the DataType on the DataTemplate to be "viewModels:ConnectionInfosVM", as I do for the content-template, but that too does not change the intellisense warnings (and I guess would be wrong, as the data type of the element really is a TabItem).
Another try was to define the DataContext by adding the following to the Button element definition:
<Button.DataContext>
<viewModels:ConnectionInfosVM/>
</Button.DataContext>
But this too, does not get rid of the warnings.
I found a solution at least for the above described problem. I was setting the DataContext of the window in question in the code-behind. The TabControl in my example just inherits that DataContext, which is a ConnectionInfosVM.
In order to fix those warnings (and of course for the gained flexibility in providing a DataContext by a Locator), I defined the DataContext in XAML like so:
<UserControl ...
DataContext="{Binding Source={StaticResource mainViewModelLocator}, Path=ConnectionInfosVM}">
It seems that intellisense is now able to resolve those references. But on the other side, I also changed the TextBlock contained within the DataTemplate to the following:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=DataContext.Address}" />
Now the DataContext of such a TabItem is actually a different type, as it is the type of the backing object for the TabItem. So in this case, the Property Address cannot be found by intellisense.
So, the question kind of remains, is there a possibility to define the type of the DataContext within a binding defined in a DataTemplate?
I needed to add the mc:Ignorable="d" attribute to the Window tag. Essentially I learned something new. The d: namespace prefix that Expression Blend/Visual Studio designer acknowledges is actually ignored/"commented out" by the real compiler/xaml parser!
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
The following was taken from
Nathan, Adam (2010-06-04). WPF 4 Unleashed (Kindle Locations 1799-1811). Sams. Kindle Edition.
Markup Compatibility
The markup compatibility XML namespace (http://schemas.openxmlformats.org/markup-compatibility/2006, typically used with an mc prefix) contains an Ignorable attribute that instructs XAML processors to ignore all elements/attributes in specified namespaces if they can’t be resolved to their .NET types/members. (The namespace also has a ProcessContent attribute that overrides Ignorable for specific types inside the ignored namespaces.)
Expression Blend takes advantage of this feature to do things like add design-time properties to XAML content that can be ignored at runtime.
mc:Ignorable can be given a space-delimited list of namespaces, and mc:ProcessContent can be given a space-delimited list of elements. When XamlXmlReader encounters ignorable content that can’t be resolved, it doesn’t report any nodes for it. If the ignorable content can be resolved, it will be reported normally. So consumers don’t need to do anything special to handle markup compatibility correctly.

Binding to property of first item in related (detail) collection

Similar to WPF: How to bind to only one item in a collection, not using ItemsControl since I don't want to display all of them, except the collection is related to the main binding item. All the data come in via EntityFramework. As with the linked question, the xaml explains it best:
<StackPanel Grid.Row="1" Orientation="Horizontal" DataContext="{Binding CurrentCustomer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Total Orders:" />
<TextBlock Text="{Binding Orders.Count}" />
</StackPanel >
<StackPanel Orientation="Horizontal">
<TextBlock Text="First Order:" />
<TextBlock Text="{Binding Orders.First.OrderDate}" />
</StackPanel >
</StackPanel>
As you can see, I guessed that since I could get a count of the related orders using 'Orders.Count', I tried to utilise the same linq syntax to try to retrieve Orders.First (I also tried Orders.FirstOrDefault), but this doesn't work.
Looking at the linked question, I tried Sheridan's '[]' syntax, but Orders[0].OrderDate give an output error of: System.Windows.Data Error: 40 : BindingExpression path error: '[]' property not found on 'object' ''HashSet'1' (HashCode=37425772)'. BindingExpression:Path=Orders[0].OrderDate; DataItem='Customer_<BigLongIdentityString>' (HashCode=21972018); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String').
Looking at the msdn article he linked to, I tried wrapping the expression in brackets (as per attached properties) and using slashes (as for when the source is a collection). Not unexpectedly, these failed too.
Can someone show me how I get to the property of the first item in a related collection?
If possible, you can always add a new property to your model that exposes the first item:
public Order FirstItem
{
get { return this.Orders.First(); }
}
Then, you can just bind to that new property.
Also, this answer is highly relevant. A hashset may not be a good fit, here. There's no indexer. I generally stick to ObservableCollections.
Also, with regard to EF generation compatible with databinding, take a look at this MSDN article. Read the section called
Updating code generation for data binding
You can mess with the T4 template and per the article,
Find and replace the first occurrence of “HashSet” with
“ObservableCollection”.
Happy WPF'ing.

Failing to successfully expose my ViewModel data to my View for XAML binding

I'm building a Windows Universal app and trying to expose data from my ViewModel to my View so that I can bind it to XAML elements. I have completely commented out all of my code at this point and am just writing lines of test code to try and get it to work, that is what is in the examples below. Binding directly from the View (if I create an object there as a test) does work.
Please help me to understand where I am going wrong, I think I've read every binding tutorial on the internet and still just don't get it.
View (MainPage.xaml.cs):
public MainPage()
{
InitializeComponent();
DataContext = new MainViewModel();
}
ViewModel (MainViewModel.cs):
public class MainViewModel
{
public Term newTerm = new Term
{
TermName = "Table",
TermDescription = "You eat dinner on it"
};
}
XAML (MainPage.xaml):
<StackPanel DataContext="{Binding newTerm}" x:Name="mvvmStack" Orientation="Vertical">
<TextBlock x:Name="mvvmTermName" Text="{Binding TermName, FallbackValue='Fallingback'}" />
<TextBlock x:Name="mvvmDescription" Text="{Binding TermDescription, FallbackValue='Fallingback', TargetNullValue='Unknown'}" />
</StackPanel>
The error I get is:
Error: BindingExpression path error: 'newTerm' property not found on ''. BindingExpression: Path='newTerm' DataItem=''; target element is 'Windows.UI.Xaml.Controls.StackPanel' (Name='mvvmStack'); target property is 'DataContext' (type 'Object')
I have read about this type of error and although I have some idea of what it is trying to say I cannot work out how to fix it. I'm very much a complete beginner with coding, especially C# so please take that into account when answering :-)
Just try to change it from field to a property and it will be working correctly. You can't bind to fields.
EDIT:
private Term _term;
public Term NewTerm{
get{return _term;}
set
{
_term= value;
OnPropertyChanged("Term");
}
}
if you need to add notify the view of changes in the viewmodel you need to implement INotifyPropertyChanged.
check this answer it will provide an example for property changed. https://stackoverflow.com/a/27685925/1448382
If you want to bind the view to sub properties, you have two options depending on the situation:
1- Relative Binding: this scenario is used when you will not modify the properties inside the Term object from the ViewModel i.e. they will be just initialized in the viewmodel and can be modified in the view, just like the way you are doing it. Plesae note, that anything you need to bind to should be a property and not a field.
2- Binding to Viewmodel directly: this scenario is used when you will modify the properties inside the Term object from the Viewmodel after the view load. This way you will need to add properties to the viewmodel for the properties TermName and TermDescription.
public string TermName{
get{return NewTerm.Name;}
set{NewTerm.Name = value;
OnPropertyChanged("TermName");
}//The same is applied for TermDescription
But be aware that you will need to remove the binding on the Stackpanel object since you have defined the properties directly in the Viewmodel.
Try something like that:
<Page.Resources>
<viewModels:MainViewModel x:Key="MainViewModel" />
</Page.Resources>
And then:
<StackPanel x:Name="mvvmStack" Orientation="Vertical">
<TextBlock x:Name="mvvmTermName" Text="{Binding newTerm.TermName, Source={StaticResource MainViewModel} FallbackValue='Fallingback'}" />
<TextBlock x:Name="mvvmDescription" Text="{Binding newTerm.TermDescription, Source={StaticResource MainViewModel} FallbackValue='Fallingback', TargetNullValue='Unknown'}" /></StackPanel>
Of cource newTerm should be an property with INotifyChanged

Josh Smith MVVM Demo app

In MainWindow we have:
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Workspaces"
Style="{StaticResource MainHCCStyle}"
/>
In the resources:
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
And in the article says:
A typed DataTemplate does not have an x:Key value assigned to it, but
it does have its DataType property set to an instance of the Type
class. If WPF tries to render one of your ViewModel objects, it will
check to see if the resource system has a typed DataTemplate in scope
whose DataType is the same as (or a base class of) the type of your
ViewModel object. If it finds one, it uses that template to render the
ViewModel object referenced by the tab item's Content property.
My question is:
How does the template know that the type is a collection of workspaces (WorkspaceViewModel)?
It doesn't need to, in the code you've posted. In your sample, you have given a strict value to your content template: you've explicitly used {StaticResource WorkspacesTemplate}, and so a resource with the key of "WorkspacesTemplate is looked up.
Because you've explicitly set the template, it doesn't matter what the intended type is: it'll try to display any object in your Content using the template you've set - with varying degrees of success if you use a type that doesn't match!
In the alternate method you mention - with a "typed DataTemplate", you would declare your datatemplate with <DataTemplate DataType="{x:Type l:WorkSpace}" />. Note that there is no x:Key (and also that I've assumed you have a namespace l mapped to your local code). What happens here is that WPF automatically sets the key of your resource to the DataType (important to note: a resource key doesn't have to be a string!).
Then, when you declare your HeaderedContentControl, you can leave out setting the ContentTemplate. At runtime, when the control is rendered, WPF will check the type of the Content object and find that it is WorkSpace, and it'll then look up a resource with x:Key="{x:Type l:WorkSpace}" - which will match your typed template.
This is a useful way of making consistent representations of data throughout your application, since a typed DataTemplate will be used automatically by any content-presenting control throughout your application.
WPF doesn't really care about the concrete type, it's just need to be some IEnumerable of something, WPF uses the type descriptor to know what the ui binding with.

how do i force the converter to execute when the property is changed?

This is my image in XAML:
<Image Margin="0"
Stretch="UniformToFill"
Source="{Binding '', Converter={StaticResource byteArrToBitmap}}">
<ToolTipService.ToolTip>
<Border BorderBrush="#FF3D3D3D" Background="#FFFFE1E1">
<TextBlock Text="{Binding PhotoDescription, TargetNullValue=No description}"
Width="170"
Height="Auto"
FontFamily="Georgia"
TextWrapping="Wrap"
Foreground="#FF373737"/>
</Border>
</ToolTipService.ToolTip>
</Image>
This Image is inside DataTemplate of listbox. As you can see I have source set to {Binding ''} which means it is bound to datacontext and not to the actual property that I want to bind. This is essential because I have some logic being performed based on which I am returning an image.
I am downloading the images on the fly from webservices and it returns a byte[]. I have INotifyPropertyChanged implemented in the class. However, as I have binding setup to the DataContext, the converter does not reexecute itself when the byte[] is downloaded in asynchronous manner.
It's a verty bad idia to bind something to the DataContext it self.. right now Silverligth 4 do not implement INotifyPropertyChanged for DataContext, so you have two options:
1) wait for Silverligt 5:
Silverlight 5–Features list
The DataContextChanged event is being introduced. Markup extensions allow code to be run at XAML parse time for both properties and event handlers, enabling cutting-edge MVVM support.
2) create some object that implement INotifyPropertyChanged, create some property, and bind to that property...
I believe you just want
{Binding Converter={StaticResource byteArrToBitmap}}
Not
{Binding '', Converter={StaticResource byteArrToBitmap}}
As not specifying any property path will bind to the DataContext. I have no idea what {Binding ''} does, but it's not standard practice. I'm surprised it doesn't throw an exception, actually.
That said, the way I would handle this is to have a wrapper object, which has a property representing the byte array - that way you can raise INotifyPropertyChanged events in a more straightforward way. I believe there's a way to invalidate the whole object, but I don't recall what it is.
I am assuming that your data context is the byte[] that you want to convert. So you have to ensure that the PropertyChanged event is raised whenever the asynchronous download is complete. Also, ensure that the event is raised on the Main Thread and not on a Worker or Background thread.
If you change your binding to be bound to some property, even if its unnecessary in the scope of your application you can indirectly cause the Converter to reevaluate when that property changes. That property needs to exist on an object that implements INotifyPropertyChanged.
I can give source code if needed.

Categories

Resources