I've got a style setter in a themes xaml file. I'm trying to bind the value of the Setter to a bool peoperty in a view model.
I've got the namespace to the view model in themes:
xmlns:propertyGrid="clr-namespace:MY.App.Controls.PropertyGrid;assembly=MY.APP.Controls"
and the binding in the style:
<Setter Property="IsExpanded" Value="{Binding Source={StaticResource propertyGrid:PropertyGridViewModel}, Path=AreCategoriesAutoExpanded}"/>
Finally in the viewmodel I just have an auto property:
public bool AreCategoriesAutoExpanded { get; set; }
However I get an exception at run time:
Cannot find resource named 'propertyGrid:PropertyGridViewModel'. Resource names are case sensitive
If I try to use a dynamic resource resource it compains that I can only bind to a dp. What is wrong with this binding? Is there something I'm missing?
This will only work if your ViewModel is a static class with a static property, like this:
<Setter Property="IsExpanded" Value="{Binding Source={x:Static propertyGrid:PropertyGridViewModel.AreCategoriesAutoExpanded}"/>
You were missing the 'x:Static' bit, which should fix it.
Related
I have a function in my UI where I want to be able to collapse/make visible a text message depending on the value of a custom property in my window object.
Using online references, I have come up with this code-behind to register the property:
public bool ValidInterval
{
get { return pValidInterval; }
}
private bool pValidInterval = true;
public static readonly DependencyProperty ValidIntervalProperty = DependencyProperty.Register("ValidInterval", typeof(bool), typeof(Settings), new UIPropertyMetadata(true));
And this corresponding XAML for the label:
<Label Name="DynamicWarning" Content="Time interval must be a valid positive integer.">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ValidInterval}" Value="true">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ValidInterval}" Value="false">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
Unfortunately, this does not work. I can get it to set the parameter depending on the initial value of the property, but doesn't update the visibility dynamically like I want. I have been looking at this for an hour and what I have seems consistent with examples I am finding online for similar operations. Am I doing something wrong, or can you not update the visibility on the fly? If the latter, how do I achieve an equivalent effect?
ΩmegaMan's answer is correct. But I wanted to clarify, you don't need a backing field with a dependency property, the static dependency property IS the backing field.
public bool ValidInterval
{
get { return (bool)GetValue(ValidIntervalProperty); }
set { SetValue(ValidIntervalProperty, value); }
}
However, if you aren't in need of a dependency object specifically, then you may just want to use INotifyPropertyChanged as ΩmegaMan has it right. Dependency properties are typically used when you need to bind another property to them, such as when making your own custom control. For example Visibility itself is the dependency property in your example and ValidInterval just needs to be a normal property that invokes the NotifyPropertyChanged event.
You need for the holder class of the properties which are bound to the page, to adhere to INotifyPropertyChanged Interface and implement it.
That process informs the bound controls on the page that something has changed, and when it has changed, then the control is "notified" of the change; then it reads afresh the property it is bound to.
For WPF/Xaml they specify the seperation of data concerns for the views to business logic, is done by implementing the Model-View-ViewModel or MVVM pattern.
The link provided is dry, and there are other resources which can describe on the net, but it simply says put all your business logic that is bound from the View to a separate View Model Class; which is instantiated on your View.
I provide a basic example, any version of .Net can be used, on my blog:
MVVM Example for Easier Binding
I know that the ListBox has both a SelectedItem and SelectedItems attribute and that only the SelectedItem attribute can be used with databinding. However, I've read in multiple locations that by setting up a setter like so
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
and then by adding the following property
public IEnumerable<Item> SelectedItems
{
get
{
return Items.Where(x => x.IsSelected);
}
}
I can use the SelectedItems property to get the all items that were selected. I mainly have two questions regarding this:
Where does the Item and Items in the property come from? I've not been able to find a using directive that will remove the error preventing me from using it.
Is this the recommended way to do what I'm trying to achieve. If not, then what would you recommend?
Oh, before I forget I thought I should mention that I'm using both MVVM Light and Fody's PropertyChanged.
I'm not quite sure about the articles and their exact solution for doing so but my speculations is this:
The whole page has a ViewModel named MyPageViewModel.
MyPageViewModel has an ObservableCollection named Items.
Item is ViewModel type (derived from DependencyObject)
Item has a DependencyProperty named IsSelected
Given these assumptions you can see where can all things fit.
If the DataContext of the whole page is MyPageViewModel, then in this xaml code first IsSelected refers to a property of ListBoxItem, and the second one refers to a property in ViewModel of Item.
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
//This is inside MyPageViewModel
//Item is a ViewModel type
public IEnumerable<Item> SelectedItems
{
get
{
//Items is ObservableCollection<Item>
return Items.Where(x => x.IsSelected);
}
}
//This is also inside MyPageViewModel
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items { get { return _items; } }
The whole scenario can go the other way too. I mean it can be implemented in View instead of ViewModel. Maybe derive from ListBox and override a few things including SelectedItems. But adding these sorts of complexities are better done to ViewModel than View.
I have a UserControl that needs to be bind with two DataContext on the basis of a checkbox.
This UserControl have to display the data about the application (global) or selected DataGridRow.
class Person
{
public string Name {get; set;}
public string Age {get; set;}
}
UserControl has only two text fields to display name and Age. If "Global" checkbox is checked, then I want to bind this usercontrol with the property of APerson (of Person class) in MainViewModel and if it is unchecked then I have to bind the UserControl with the SelectedItem in DataGrid. SelectedItem is also a Person type
Basically you could just play with your ViewModel to get what you want. Here is one way to do it.
You would be binding the Checkbox.IsChecked to 'IsGlobal' property in the ViewModel.
Then you would be binding the userControl to another property, say SomePerson in the ViewModel.
Lastly, in the setter of IsGlobal you would change the SomePerson to either APerson or SelectedItem in your datagrid depending on the boolean state of IsGlobal.
[Adding this here since you want a way to do it purely in XAML. I think that insisting on pure XAML here is not essential and #bit's answer is the right way to go, IMO.]
You can use style to have triggers that do the change.
Let's say your UC is called MyUC and currently you have an instance of it similar to: <local:MyUC/> in some other view/UC/window. You can change the instance to look like so:
<local:MyUC>
<local:MyUC.Style>
<Style TargetType="{x:Type local:MyUC}">
<Setter Property="DataContext" Value="{Binding SelectedItem, ElementName=MyDataGrid}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyCheckbox, Path=IsChecked}"
Value="True">
<Setter Property="DataContext" Value="{Binding APerson}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</local:MyUC.Style>
</local:MyUC>
I'm changing here the data context property, but you can change any other dependency property on MyUC.
Again, I think this is a less favorite approach to tackle this functionality, but it's pure XAML.
I'm struggling for about 14 days now with a simple task: In database, I have definitions for hardware categories. For example :
HDD
Internal
External
Flash
This list is in database defined like this:
[ID - ParrentID - Name] : 1 - 0 - HDD, 2 - 1 - Internal, 3 - 1 - External, 4 - 1 - Flash.
Through Entity Framework I get these rows into my application. From this flat data I then create structured object which is my DataModel. This model is defined as follows :
public class Category
{
private int _id = -1;
private string _name = "";
private List<Category> _subCategories = null;
// property getters and setters, constructors, and bool HasSubCategories
}
Now, from these I create ViewModel called SubCategoryViewModel to which is binded my TreeView. So, I can view my categories in treeview and with my defined and maintained hierarchy. This works just fine. In SubCategoryViewModel is defined a Command through Attached Behavior for MouseDoubleClick which is also binded to TreeView. So, when user doubleclicks on Item, in SubViewCategoryModel defined method will execute particular code. List of SubCategoryViewModel is nested in HWDocumentViewModel which is a main ViewModel for my window.
What I need now is obvious : When user doubleclicks on item in TreeView, I need to load items from database and show them in ListView. My opinion is, that in HWDocumentViewModel I need to define an collection of Items and load them accordingly to selected category in ListView. But, I don't know how to execute a method on HWDocumentViewModel from SubCategoryViewModel. Because : TreeView is binded to list of SubCategoryViewModel items, so when DoubleClick occurs, the method on SubCategoryViewModel is executed. I'm searching for a way, how to execute a method on main ViewModel (HWDocumentViewModel).
I tried this approach :
I created a property : public static SubCategoryViewModel SelectedCategory on HWDocumentViewModel. When doubleclick occurs, I set this property from SubCategoryViewModel as this. So, in this property is object, which executed doubleclick event delegate. Great, now I have in HWDocumentView model an object, which user selected.
So, I need to load items to ListView. But, will I load them from method in SubCategoryViewModel ? I don't think so. Instead I should load them from Main View Model by creating a ViewModel for them and bind it to ListView, right ? But, how can I from SubCategoryViewModel call a method in HWDocumentViewModel ? Should I write a static method
on a HWDocumentViewModel which will be accessible from SubCategoryViewModel ?
Or is there a way, how to call Command defined on HWDocumentViewModel from SubCategoryViewModel ?
Or generally, did I take a right approach to create a Warehouse-like application in WPF ?
Thanks a lot.
EDIT: XAML for my TreeView looks like this :
<TreeView x:Name="tvCategories" Background="White" ItemsSource="{Binding Categories}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="behaviors:MouseDoubleClick.Command" Value="{Binding MouseDoubleClickCommand}" />
<Setter Property="behaviors:MouseDoubleClick.CommandParameter" Value="{Binding}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type localvm:SubCategoryViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CategoryName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I'm not sure I see the problem. You have a tree of subcategories and when one is selected, the appropriate SubCategoryViewModel sets itself as SelectedCategory on the main HWDocumentViewModel. That seems like a reasonable approach.
So why do you need to call a command? Why can't you just load the new list in HWDocumentViewModel in response to a change of its SelectedCategory property (ie in the setter)?
If you really must use a command to invoke the load, then simply keep a reference to your main HWDocumentViewModel in each SubCategoryViewModel, and invoke the command with a simple:
_mainViewModel.LoadCategoryCommand.Execute();
With MVVM and trying to communicate between View and ViewModel or between ViewModels a publisher/Subscriber setup works well or a messaging paradigm like what's found in MVVMLight or Prism. I posted an answer on MVVM Light's messaging setup here
In the message you can send an object that holds any data you would like to send back and forth between the view models.
I highly recomend using a framework when working with mvvm it makes like much easier. MVVM Framework Comparison is a link to an answer that goes through a comparison of some of the major frameworks.
I'm having an issue when trying to do something which should be as easy as. I've attempted to use a Trigger based on a DependencyProperty or a DataTrigger - I can't get either to work.
XAML for the trigger is:
<Style x:Key="FileWatchButton" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="Main:Main.XmlFilesAvailableForLoading" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
And the associated code-behind is:
public static readonly DependencyProperty XmlFilesAvailableForLoadingProperty =
DependencyProperty.Register("XmlFilesAvailableForLoading", typeof(bool), typeof(Main));
public bool XmlFilesAvailableForLoading
{
get
{
try
{
return (bool)this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Windows.Threading.DispatcherOperationCallback)delegate { return GetValue(XmlFilesAvailableForLoadingProperty); },
XmlFilesAvailableForLoadingProperty);
}
catch (Exception)
{
return (bool)XmlFilesAvailableForLoadingProperty.DefaultMetadata.DefaultValue;
}
}
set
{
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Threading.SendOrPostCallback)delegate{ SetValue(XmlFilesAvailableForLoadingProperty, value); }, value);
}
}
Basically the dp is being set correctly by the presenter (it's based on a FileSystemWatcher class looking for one or more files) but the Trigger is not being fired. Is this a threading issue?
Thanks.
It's not clear if the code is complete, but it looks like the Property path in your trigger may be wrong. Does the button being styled have a Main property? I am guessing not; it looks like you are trying to trigger on a property of a different element, called Main -- is that right?
In any case, the namespace prefix is not required. If the button has a property named Main, then you can address this directly; if it doesn't, then the prefix won't help you.
My guess is that you probably need a DataTrigger whose binding refers to the Main element:
<local:Main Name="MyMain" ... /> <!-- this has the XmlFilesAvailableForLoading property -->
<DataTrigger Binding="{Binding XmlFilesAvailableForLoading, ElementName=MyMain}"
Value=True>
<Setter Property="Background" Value="Red" />
</DataTrigger>
On an unrelated note, you should have any non-boilerplate implementation in your DP getter and setter. Remember that the binding and styling system will bypass the getter and setter and talk directly to the underlying storage. So I'd strongly advise changing these back to just plain GetValue and SetValue calls.