ContentControl visibility binding not working - wpf

I have a visibility binding like this inside a UserControl.
<Grid x:Name="_dockPanelMain">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" cal:RegionManager.RegionName="{x:Static Member=consts:RegionNames.MainMenu}" DockPanel.Dock="Top" Visibility="{Binding MainMenuVisibility, Mode=TwoWay}"/>
<ContentControl Grid.Row="0" x:Name="dockManagerModules" cal:RegionManager.RegionName="{x:Static Member=consts:RegionNames.Modules}" Visibility="{Binding ModulesVisibility, Mode=TwoWay}"/>
<ContentControl Grid.Row="1" x:Name="dockManagerStatusBar" cal:RegionManager.RegionName="{x:Static Member=consts:RegionNames.StatusBar}"/>
</Grid>
This user control is given a ViewModel, of which relevant part is
private void SwitchMenuAndModulViews(object sender)
{
if (ModulesVisibility == Visibility.Visible)
{
ModulesVisibility = Visibility.Collapsed;
MainMenuVisibility = Visibility.Visible;
}
else
{
ModulesVisibility = Visibility.Visible;
MainMenuVisibility = Visibility.Collapsed;
}
}
private Visibility _modulesVisibility = Visibility.Visible;
public Visibility ModulesVisibility
{
get { return _modulesVisibility; }
set
{
_modulesVisibility = value;
RaisePropertyChangedEvent(() => ModulesVisibility);
}
}
private Visibility _mainMenuVisibility = Visibility.Collapsed;
public Visibility MainMenuVisibility
{
get { return _mainMenuVisibility; }
set
{
_mainMenuVisibility = value;
RaisePropertyChangedEvent(() => MainMenuVisibility);
}
}
When SwitchMenuAndModulView gets called, nothing happens. The variables change, but the binding does not work and both ContentControls have Visibility set as Visible at all times, which I guess is the default and none of them ever change to Collapsed.

SOLVED:
I solved it myself, the problem was not in the binding itself but in the way the application dealt with instantiating and showing the UserControl.
I had an instance of a UserControl A that had this UserControl B as a part of its content. Naturaly, A created an instance of B upon its creation.
Later, I replaced the instance of B in A by a different instance of B (the one with my viewModel) but I probably forgot to tell the visual tree about the change, therefore visualy it still showed the old B instance whose binding did not lead anywhere.
I kind of wish an application threw an exception when a blind binding happens (as in, it binds to an element that is either not instantiated or misspelled) instead of just leaving the default there. This behaviour of XAML generates so many bugs.

Related

Wpf Databound TextBlock not updating [duplicate]

This question already has an answer here:
Binding works without INotifyPropertyChanged, why?
(1 answer)
Closed 5 years ago.
I have a simple WPF window with a slider and two textblocks. As the slider moves it updates a data bound object. Now the first textblock updates while the second does not. Why?
You may say there is no INotifyPropertyChanged here. But then why is the first updating? I have pulled my hair enough. Please help.
My WPF app in all its glory is as follows.
<Window x:Class="DataTriggerDemo.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:DataTriggerDemo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Slider x:Name="MySlider" Margin="5" Minimum="0" Maximum="100"
Value="{Binding TheValue}"/>
<TextBlock Grid.Row="1" Text="{Binding TheValue}" />
<TextBlock Grid.Row="2" Text="{Binding TheValueTwice}" />
</Grid>
</Window>
And now the code behind.
using System.Windows;
namespace DataTriggerDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new DataObject();
}
}
public class DataObject
{
private int _theValue;
public int TheValue
{
get { return _theValue; }
set {
_theValue = value;
TheValueTwice = _theValue * 2;
}
}
private int _theValueTwice;
public int TheValueTwice
{
get {
return _theValueTwice;
}
set {
_theValueTwice = value;
}
}
}
}
Actually you are encountering a another hidden aspect of WPF, that's it WPF's data binding engine will data bind to PropertyDescriptor instance which wraps the source property if the source object is a plain CLR object and doesn't implement INotifyPropertyChanged interface. And the data binding engine will try to subscribe to the property changed event through PropertyDescriptor.AddValueChanged() method. And when the target data bound element change the property values, data binding engine will call PropertyDescriptor.SetValue() method to transfer the changed value back to the source property, and it will simultaneously raise ValueChanged event to notify other subscribers (in this instance, the other subscribers will be the TextBlocks within the ListBox.
Please refer to: https://social.msdn.microsoft.com/Forums/vstudio/en-US/9365bb6a-b411-4967-9a03-ae2a810fb215/data-binding-without-inotifypropertychanged?forum=wpf

Bind user control dependency properties in MVVM style Windows Phone app

I'm having some issues with binding some custom controls in a Windows Phone app right now. Usually this is never an issue but apparently my mind can't comprehend this today.
So I'm doing an MVVM style setup which is good. I have my page with a view and also a viewmodel. Now on a WebClient callback I assign the dataContext of my view to the list of models in my ViewModel, nice and simple thus far...now in my view I created a ListBox with a custom control in the datatemplate which is basically a cell in the list. I once again set my user controls dataContext to binding, and binding all the models values to the regular UI elements works no problem.
Here's a sample:
<Grid Grid.Column="0">
<Image Source="{Binding SmallPath}" VerticalAlignment="Top"/>
</Grid>
<Grid Grid.Column="1">
<StackPanel Margin="12,0,0,0">
<TextBlock x:Name="MemberId_TextBlock" Text="{Binding MemberId}" FontSize="28"
Margin="0,-8,0,0"
Foreground="{StaticResource PhoneForegroundBrush}"/>
<StackPanel Orientation="Horizontal" Margin="0,-11,0,0">
<TextBlock Text="{Binding DaysReported}" FontSize="42"
Margin="0,0,0,0"
Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="days" FontSize="24"
Margin="3,19,0,0"
Foreground="{StaticResource PhoneSubtleBrush}"/>
</StackPanel>
</StackPanel>
</Grid>
That's in my user control, and here's the the view where the usercontrol is housed:
<Grid x:Name="LayoutRoot" Background="Transparent">
<ListBox Name="TopSpotter_ListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!--<TextBlock Text="{Binding MemberId}"/>-->
<controls:TopSpotterItemControl DataContext="{Binding}"/>
<Grid Height="18"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Now this is good enough but what I want to do in my view is set data from my model like Booleans that determine whether or not I should show certain Grids etc. So if I try to set a dependency property explicitly in my control it fires and will run logic in the Getter/Setters for instance. HOWEVER if I try to set these custom objects from a binding source it won't actually set.
Here's what works:
<controls:TopSpotterItemControl ChampVisibility="True">
This way will trigger the ChampVisibility property and then in the code behind of the user control I can set visibilities.
Here's what fails but I want to work:
<controls:TopSpotterItemControl ChampVisibility="{Binding IsChamp">
In addition I can still set the DataContext to {Binding} and the result will be unchanged.
In this scenario IsChamp is part of my model that I would like to bind to this user control which I guess comes from the dataContext being set on the view from the viewModel. I'm not sure what I can do to get this so the bindings work etc. without having to set custom properties.
Finally, here's my user control:
public partial class TopSpotterItemControl : UserControl
{
public string MemberId
{
get
{
return this.MemberId_TextBlock.Text;
}
set
{
this.MemberId_TextBlock.Text = value;
}
}
public bool ChampVisibility {
set
{
if (value)
{
this.Champ_Grid.Visibility = System.Windows.Visibility.Visible;
}
}
}
public static readonly DependencyProperty MemberNameProperty =
DependencyProperty.Register("MemberId", typeof(string), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public TopSpotterItemControl()
{
InitializeComponent();
}
}
Bit long winded and I hope I made things on the issue clear. My one major hang up so far, and I'd like to abstract as much control as I can to the user control via dependency properties explicitly set in xaml, rather than setting up binding in its xaml that depend on the knowledge of a model. Thanks!
Your DependencyProperty is badly formed. (I also don't see Champ_Grid defined in your class or XAML, but I assume that is an ommission)
Setting ChampVisibility = true in code works because it is unrelated to the DependencyProperty.
You can tell easily because the default value for your DP is invalid. It will compile, but the instance constructor will through an exception if it is ever invoked.
new PropertyMetadata(null)
bool = null = exception
If you call GetValue(TopSpotterItemControl.ChampVisibilityProperty) from somewhere you can confirm all of the above.
You should make changes to instance fields in the property changed handler and declare the property like the following, it will work:
Note that the property has to change (not just be set) for the event to be raised.
public bool ChampVisibility
{
get { return (bool)GetValue(ChampVisibilityProperty); }
set { SetValue(ChampVisibilityProperty, value); }
}
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility ", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(true, (s, e) =>
{
TopSpotterItemControl instance = s as TopSpotterItemControl;
instance.Champ_Grid.Visibility = instance.ChampVisibility ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
}));
Incidentally, your MemberId DependencyProperty is also completely wrong and cannot work.
Note:
The Binding on your TextBox works, because it is binding to the DataContext (your model), so it probably shows the right value.
The Dependency property in your UserControl will never be set though.
Use the propdp code-snippet in Visual Studio so you dont have to concern yourself with the complexities of Dependency Property declaration.
Also check this out for more info about Dependency Properties

Binding element visibility to a ViewModel property, with design-time support

I've got a WPF application using Caliburn.Micro. I want to be able to overlay the application with a shadow and progress ring (from MahApps.Metro) when I want the application to wait for some work to be done in the background.
What I have at the moment actually works but the overlay is always-on at design time. My ShellView window looks like this:
<Window ...>
...
<Grid>
...
<Rectangle x:Name="waitShadow" Fill="#3f000000" Stroke="Black" StrokeThickness="0" Visibility="{Binding IsWaiting, Converter={StaticResource BooleanToVisibilityConverter}}" Grid.RowSpan="2"/>
<ContentControl ... Visibility="{Binding IsWaiting, Converter={StaticResource BooleanToVisibilityConverter}}">
<Controls:ProgressRing ...> <!-- from MahApps.Metro -->
</Controls:ProgressRing>
</ContentControl>
</Grid>
</Window>
My ShellViewModel class has a public bool property IsWaiting and when I set it to true the shadow and ring comes up and everything is disabled. When I set it to false it goes back to normal, so the binding works (I'm using Fody with the PropertyChanged addin). The only problem is that the Visibility property isn't collapsed at design time.
Is there a better way to have an overlay that works at design time?
You can set a FallbackValue on your binding, that will Collapse it in design time
Visibility="{Binding IsWaiting, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"
You could also make IsWaiting a DependancyProperty and set the default there, But I find this the easiest solution.
FallbackValue doesn't always work, i.e. if your designer is actually bound to design time data, and FallbackValue actually modifies run-time behaviour of the binding which may be less than desirable in many situations. I made a markup extension that lets designers fiddle with the UI in the designer without worrying about messing up run-time behaviour. I wrote about it here: http://www.singulink.com/CodeIndex/post/wpf-visibility-binding-with-design-time-control
It can be used like this:
<Grid Visibility="{data:Value {Binding RootObject, Converter={StaticResource NullToVisibilityConverter}}, DesignValue=Visible}">
<TextBlock Background="Red" Text="Testing visibility" />
</Grid>
The code for ValueExtension is as follows (any updates or bug fixes will be posted to the blog so I suggest checking there for the latest version):
public class ValueExtension : MarkupExtension
{
public object DesignValue { get; set; } = DependencyProperty.UnsetValue;
[ConstructorArgument("value")]
public object Value { get; set; } = DependencyProperty.UnsetValue;
public ValueExtension() { }
public ValueExtension(object value)
{
Value = value;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var property = provideValueTarget.TargetProperty as DependencyProperty;
var target = provideValueTarget.TargetObject as DependencyObject;
if (target == null || property == null)
return this;
object value = DesignerProperties.GetIsInDesignMode(target) && DesignValue != DependencyProperty.UnsetValue ? DesignValue : Value;
if (value == DependencyProperty.UnsetValue || value == null)
return value;
if (value is MarkupExtension)
return ((MarkupExtension)value).ProvideValue(serviceProvider);
if (property.PropertyType.IsInstanceOfType(value))
return value;
return TypeDescriptor.GetConverter(property.PropertyType).ConvertFrom(value);
}
}

InvalidOperationException in databound FluidKit ElementFlow when collection is changed

I have ElementFlowContainer user control that contains ElementFlow from FluidKit.Showcase project.
<UserControl x:Class="Controls.ElementFlowContainer">
<Grid>
<-- Other controls (cut) -->
<Controls:ElementFlow x:Name="_elementFlow" ItemsSource="{Binding}" ItemTemplate="{DynamicResource TestDataTemplate}" SelectedIndex="3">
<-- Layout, Background, Camera settings (cut) -->
</Controls:ElementFlow>
</Grid>
</UserControl>
I have ObservableCollection that serves as DataContext of ElementFlow :
<Controls:ElementFlowContainer DataContext="{Binding MediaRecords}"/>
MediaRecord has image property (byte[] Content) which i want to display. Here's template :
<DataTemplate x:Key="TestDataTemplate"
DataType="{x:Type DAL:MediaRecord}">
<Border x:Name="ElementVisual" Background="White" BorderThickness="2" BorderBrush="#ff9e8028">
<Image Source="{Binding Content}" Stretch="Fill" />
</Border>
</DataTemplate>
All the stuff above is in ViewModel which is created by IoC container (MediaRecords property is null during initialization). When collection is filled with items I get
"InvalidOperationException '[Unknown]' property does not point to a DependencyObject in
path "(0)[0].(1)[1].(2).(3)[0].(4)."
This error happens in RaisePropertyChanged in property setter :
public const string MediaRecordsPropertyName = "MediaRecords";
public ObservableCollection<MediaRecord> MediaRecords
{
get { return _mediaRecords; }
set
{ if (_mediaRecords == value) { return; } _mediaRecords = value;
RaisePropertyChanged(MediaRecordsPropertyName); // error here
}
}
Any idea how to fix this?
edit
Same collection is bound to another control, so i guess this issue is concurrency related. Fixed it quick-and-dirty by maintaining second copy of collection and binding to it, but maybe there is a better way?
Solution with separate collection worked, issue now may be closed.

yet another wpf listbox refresh

As I am new to wpf i am loosing myself in the webpages about similar topic. I hope somebody could help me explain some basic stuff that I could not manage to understand.
I have a wpf application connected over a websocket to the server. The server returns me every 5 seconds a List. Every new list has nothing to do with the old one. When i get the new list, the old one is not important anymore. The only property of a Player (in the list) that interests me in his ID.
Somehow I need to refresh or update the listbox. I used the observable collection in this way:
private static ObservableCollection<Player> sample;
private static List<Player> sample2 = new List<Player>();
public List<Player> update
{
set
{
sample2 = value;
sample = new ObservableCollection<Player>((List<Player>) sample2);
onPropertyChanged(sample, "ID");
}
}
private void onPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
When debugging the propertychanged is always null. Im really lost here how to update the listbox.
The xaml of the listbox goes like this:
<DataTemplate x:Key="PlayerTemplate">
<WrapPanel>
<Grid >
<Grid.ColumnDefinitions x:Uid="5">
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" Margin="5" Grid.Column="0" Text="{Binding Path=ID}" FontSize="22" FontWeight="Bold"/>
</Grid>
</WrapPanel>
sample doesn't have a property called "ID" because sample is your collection, not your Player instance. Moreover, since you are completely replacing the collection, there is no point using an observable one. Try this:
private ICollection<Player> players = new List<Player>();
public ICollection<Player> Players
{
get { return this.players; }
private set
{
this.players = value;
// the collection instance itself has changed (along with the players in it), so we just need to invalidate this property
this.OnPropertyChanged(this, "Players");
}
}

Resources