Detect binding property changed within a user control - c#

I am working on a Windows Store app in which I have a user control as a data template inside flipview.
User Control: (ImagePage.xaml)
<UserControl
x:Name="userControl"
x:Class="MWC_online.Classes.ImagePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MWC_online.Classes"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="768"
d:DesignWidth="1366">
<Grid Background="#FFF0F0F0" Margin="4,0">
...
<Image Source="{Binding Img}" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top" />
<StackPanel x:Name="stackPanel" HorizontalAlignment="Left" Margin="984,83,0,0" Width="325">
<Grid Background="{Binding Colour}">
<TextBlock Margin="30,30,30,15" Text="{Binding TextContent1}" FontWeight="Light" TextWrapping="Wrap" Foreground="#FF00ABE8" FontSize="29" />
</Grid>
<Grid Background="{Binding Colour}">
<TextBlock Margin="30,10,30,30" Text="{Binding TextContent2}" TextWrapping="Wrap" Foreground="#FF606060" FontSize="17" />
</Grid>
</StackPanel>
</Grid>
</UserControl>
User Control Class: (ImagePage.xaml.cs)
private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){
StackPanel stackPanel = (StackPanel)d;
stackPanel.Visibility = Visibility.Collapsed;
}
public string TextContent1
{
get { return (string)GetValue(TextContent1Property); }
set { SetValue(TextContent1Property, value); }
}
public static readonly DependencyProperty TextContent1Property =
DependencyProperty.Register("TextContent1", typeof(string), typeof(ImagePage), new PropertyMetadata("", new PropertyChangedCallback(OnTextContent1Changed)));
private static void OnTextContent1Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// what I want to do is if TextContent1 or TextContent2 has no value
// turn the stackpanel visibility to collapsed
StackPanel stackPanel = (StackPanel)d;
stackPanel.Visibility = Visibility.Collapsed;
}
Everything is working fine EXCEPT the OnTextContent1Changed is not firing! so I dont know if this is the right way of doing things but basically I just want to switch an UI element within the user control ON or OFF depending on the data binding that is being fed into it.

The TextBlock doesn't have a DataContext to find the DependencyProperty on. If you give your Grid a name in ImagePage.xaml:
<Grid Background="#FFF0F0F0" Margin="4,0" x:Name="MyGrid">
Then you can set its DataContext in the ImagePage constructor in ImagePage.xaml.cs:
public ImagePage()
{
InitializeComponent();
MyGrid.DataContext = this;
}
Which tells the Grid (and its decendents) to look for Dependency Properties on the ImagePage class. With this, the Dependency Property should get bound correctly. Another problem, though, is that you're telling the DependencyProperty that it is on an ImagePage with typeof(ImagePage), but then casting it to a StackPanel, which will fail every time:
StackPanel stackPanel = (StackPanel)d; // Throws System.InvalidCastException
You could fix this by giving a name to the StackPanel and referencing it directly in your .cs file.

Related

GridView, ItemTemplate, DataTemplate binding in C# code behind

I have following working XAML and C# code behind:
<Grid x:Name="MainGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<GridView ItemsSource="{Binding}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Height="100" Width="150">
<Grid.Background>
<SolidColorBrush Color="{Binding Color}"/>
</Grid.Background>
<StackPanel>
<StackPanel.Background>
<SolidColorBrush Color="{Binding Color}"/>
</StackPanel.Background>
<TextBlock FontSize="15" Margin="10" Text="{Binding Name}"/>
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
CODE behind:
public MainPage()
{
this.InitializeComponent();
var _Colors = typeof(Colors)
.GetRuntimeProperties()
.Select(x => new
{
Color = (Color)x.GetValue(null),
Name = x.Name
});
this.DataContext = _Colors;
}
This works fine.
But I want to do all the XAML part in C# code behind. In XAML, only MainGrid will be there, all its child elements and bindings needs to be done in code behind.
I have tried something like this in MainPage_Loaded event:
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
try
{
GridView gridView = new GridView()
{
ItemTemplate = new DataTemplate
{
//Don't know what to add here
}
};
Grid grid = new Grid();
Binding bindingObject = new Binding();
bindingObject.Source = this;
grid.SetBinding(Grid.BackgroundProperty, bindingObject);
//...
// Don't know how to add grid inside gridView in Code.
//...
MainGrid.Children.Add(gridView);
}
catch (Exception ex)
{
}
}
First of all I'd like to advise you not to create elements in code unless you have a real good reason to do so.
Regardless, considering that item templates are factory-like objects for controls (you 'spawn' a new set of controls for each item). You use the FrameworkElementFactory to model the subtree and then assign that the item template's VisualTree property.

How to bind a Grid to a UserControl Property in XAML?

I have a UserControl (let's call it "CustomControl") that acts as a simple toolbar for a grid. It contains only a button to open/close (it just changes it's visibility property) a Grid.
Like this:
<UserControl ....>
...
<Grid>
<Button x:Name="btChangeState" Content="Change State" />
</Grid>
....
</UserControl>
And I have declared the DependencyProperty of a Grid like the following:
public Grid MyContent
{
get { return (Grid)GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(Grid), typeof(MyControl), new PropertyMetadata(null));
And my user control button has a simple handler to change the "MyContent" Grid Visible or collapsed:
if (MyContent.Visibility == Windows.UI.Xaml.Visibility.Visible)
MyContent.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
else
MyContent.Visibility = Windows.UI.Xaml.Visibility.Visible;
On my ViewPage, I am able to wire up the theUserControl.MyContent to theGrid in the constructor of the Page, but how can I do that in the XAML? I would like to do something like this:
<Page .....>
<StackPanel Orientation="Vertical">
<cs:CustomControl x:Name="theUserControl" MyContent="{Binding theGrid}" />
<Grid x:Name="theGrid" Height="200" Width="200" Background="Red" />
</StackPanel>
</Page>
Is it possible?
You need to use ElementName:
<cs:CustomControl x:Name="theUserControl" MyContent="{Binding ElementName=theGrid}" />

Binding property of usercontrol to data

I'm not sure of the correct terminology to use. I created a Windows Store app about a year ago and the main page was created by Visual Studio and I never changed it much. It uses a view model that works fine but I don't know enough to fix problems. Anyhow...
The page uses a GridView to display the contents of CollectionViewSource element to reference an ObservableCollection. This all works fine. The DataTemplate for one of the data items looks like this right now:
<DataTemplate x:Key="TopImageTileTemplate">
<Grid MinHeight="135" Width="350" Margin="0" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="135"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=ImagePath}" FontSize="33"/>
<usercontrols:WaitingImageControl SourcePath="{Binding Path=ImagePath}" Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center" VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}" Visibility="{Binding TypeDescription, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
<usercontrols:WaitingImageControl SourcePath="XXX" Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center" VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}" Visibility="{Binding TypeDescription, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
<ProgressRing Opacity="0.5" Foreground="#FF8A57FF" Grid.Row="0" Name="TheProgressControl" IsActive="True" Height="32" Width="32" Background="Transparent" VerticalAlignment="Center" HorizontalAlignment="Center"/>
...
</Grid>
</DataTemplate>
The problem that I have is that the data item for this contains a string called ImagePath that I want to pass into the WaitingImageControl usercontrol and it's not working. The TextBlock works fine and the text displays the ImagePath string just fine. The second WaitingImageControl works fine and the code that handle SourcePath does get passed the "XXX" just fine too. But the first WaitingImageControl never gets passed the ImagePath value from the data item.
This is some sort of binding issue and I know so little about binding that I'm to even sure what to try (or what to show in this question). given that the TextBlock binding works and the second WaitingImageControl binding works, I'm at a loss.
Here's the WaitingImageControl code for the SourcePath property:
public static readonly DependencyProperty SourcePathProperty =
DependencyProperty.Register("SourcePath", typeof(string), typeof(WaitingImageControl), new PropertyMetadata(""));
public string SourcePath
{
get { return m_SourcePath; }
set
{
if( string.IsNullOrEmpty( value ) )
return;
m_SourcePath = value;
ResourcesStore Store = new ResourcesStore();
if( Store.Count() == 0 )
{
var IgnoreMe = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () =>
{
// No progress and no image...
TheProgressControl.Visibility = Visibility.Collapsed;
TheImage.Visibility = Visibility.Collapsed;
} );
return;
}
ResourceItem Item = Store.getItemByFilename( m_SourcePath );
LocalInboxService.Instance.InboxStatusChanged -= InboxStatusChanged;
InboxStatusChanged( null );
LocalInboxService.Instance.InboxStatusChanged += InboxStatusChanged;
}
}
The code is supposed to show the Image element and hide the ProgressRing element when the image has been downloaded.
And the code for the data item, which again, works just fine when the ImagePath is passed automatically to the TextBlock:
public string ImagePath
{
get
{
return this._imagePath;
}
set
{
this._imagePath = value;
this.SetProperty(ref this._imagePath, value);
}
}
Any help is appreciated making the ImagePath to SourcePath binding (below) work:
<usercontrols:WaitingImageControl SourcePath="{Binding Path=ImagePath}"
Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center"
VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}"
Visibility="{Binding TypeDescription, RelativeSource={RelativeSource
Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
After hours of searching, I found a StackOverflow answer to a similar question. The answer was to add a PropertyChanged function to the Propertymetadata. I'm not sure yet what this actually means or why it is only needed here, but it works properly:
public static readonly DependencyProperty SourceImageResourceIdProperty =
DependencyProperty.Register("SourceImageResourceId", typeof(string), typeof(WaitingImageControl), new PropertyMetadata( string.Empty, OnSourcePathPropertyChanged ));
private static void OnSourcePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as WaitingImageControl).SourceImageResourceId = e.NewValue.ToString();
}
The OnSourcePathPropertyChanged function gets called and the property gets set like it should.
Now I just hope that it wasn't one of the twenty other experiments that actualy fixed this!

How can I have a ControlTemplate that only creates a container for the default, unaltered visual tree of a control?

I'm trying to figure out how to change a control's template to something that will make it held inside a Grid, like this:
<ControlTemplate x:Key="containedTemplate">
<Grid>
<!-- place templated control here -->
</Grid>
</ControlTemplate>
I of course want any of the inner control's properties to be synced automatically with the templated control.
Can this be done at all?
Here's an hypothetical example for a TextBox template:
<ControlTemplate x:Key="textTemplate" TargetType="{x:Type TextBox}">
<Grid Background="Red">
<TextBox Name="InnerTextBox" Margin="5,5,5,5"/>
</Grid>
</ControlTemplate>
Now if I did apply the template on a TextBox instance like this:
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
... then the control would magically be a Grid, containing a TextBox with a few margins and whose Text's property would be bound to MyTextProperty of whatever DataContext instance has been set:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
If I had the following code:
<StackPanel>
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding MyOtherTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding YetAnotherTextProperty}" Template="{StaticResource textTemplate}"/>
</StackPanel>
The resulting tree would be this:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<StackPanel>
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding MyOtherTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding YetAnotherTextProperty}" Margin="5,5,5,5"/>
</Grid>
</StackPanel>
In these examples you can see that the TextBox's Text property is correctly propagated down to the "inner" TextBox instance. The control's default visual tree is also preserved (borders, typing area, etc.).
I'm aware of template parts but as I said I'm trying to find a global approach here, and I DO NOT want to change the control's appearance; only put it inside a container.
frankly, this question exhausted me, i have this only answer but not convince me a lot.
first you should create multi ControlTemplates for each control that you want to set your template then create this class
public class ControlTemplateConverter
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ControlTemplateConverter), new UIPropertyMetadata(false, IsEnabledChanged));
private static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ControlTemplate t;
if (d == null) return;
if (d is TextBlock)
t = App.Current.FindResource("TextBoxTemplate") as ControlTemplate;
else if (d is CheckBox)
t = App.Current.FindResource("CheckBoxTemplate") as ControlTemplate;
// and So On
(d as Control).Template = t;
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
}
and your control should like this:
<TextBox local:ControlTemplateConverter.IsEnabled="True"></TextBox>
<CheckBox local:ControlTemplateConverter.IsEnabled="True"></CheckBox>

silverlight 2 binding data to transforms?

I am working on creating a tag cloud in Silverlight 2 and trying to bind data from a List collection to a Scale transform on a TextBlock. When running this I get an AG_E_PARSER_BAD_PROPERTY_VALUE error. Is it possible to data bind values to transforms in Silverlight 2? If not could I do something to the effect of FontSize={Binding Weight*18} to multiply the tag's weight by a base font size? I know this won't work, but what is the best way to calculate property values for items in a DataTemplate?
<DataTemplate>
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" d:IsStaticText="False" Text="{Binding Path=Text}" Foreground="#FF1151A8" FontSize="18" UseLayoutRounding="False" Margin="4,4,4,4" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Path=WeightPlusOne}" ScaleY="{Binding Path=WeightPlusOne}"/>
</TransformGroup>
</TextBlock.RenderTransform>
The issue seems to be Rule #1 from this post:
The target of data binding must be a FrameworkElement.
So since ScaleTransform isn't a FrameworkElement it doesn't support binding. I tried to bind to a SolidColorBrush to test this out and got the same error as with the ScaleTransform.
So in order to get around this you can create a control that exposes a dependency property of your tag data type. Then have a property changed event that binds the properties of your tag data to the properties in the control (one of which would be the scale transform). Here is the code I used to test this out.
items control:
<ItemsControl x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TagControl TagData="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
tag control xaml:
<UserControl x:Class="SilverlightTesting.TagControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<TextBlock x:Name="text" TextWrapping="Wrap" FontSize="18" Margin="4,4,4,4">
<TextBlock.RenderTransform>
<ScaleTransform x:Name="scaleTx" />
</TextBlock.RenderTransform>
</TextBlock>
</UserControl>
tag control code:
public partial class TagControl : UserControl
{
public TagControl()
{
InitializeComponent();
}
public Tag TagData
{
get { return (Tag)GetValue(TagDataProperty); }
set { SetValue(TagDataProperty, value); }
}
// Using a DependencyProperty as the backing store for TagData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TagDataProperty =
DependencyProperty.Register("TagData", typeof(Tag), typeof(TagControl), new PropertyMetadata(new PropertyChangedCallback(TagControl.OnTagDataPropertyChanged)));
public static void OnTagDataPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var tc = obj as TagControl;
if (tc != null) tc.UpdateTagData();
}
public void UpdateTagData()
{
text.Text = TagData.Title;
scaleTx.ScaleX = scaleTx.ScaleY = TagData.Weight;
this.InvalidateMeasure();
}
}
Seems like overkill for just setting a single property, but I couldn't find an easier way.

Categories

Resources