I have a feeling my base issue with this problem is binding some internal properties.
In the usercontrol I have a rectangle that contains a linear gradient. I created a dependency property to be able to give a value (0 to 1) to specify where the gradient line should be in the rectangle. To adjust this value dynamically I connected a slider to it for testing.
I also added some feedback textblocks to let me know how some of the data is flowing and if it is flowing. And from that I can tell my binding to my GradientStops.offset values from my user control properties are not working. How do I get the user control to update the rectangle gradient by just changing RectangleLevel.RectLevel value?
USERCONTROL XAML
<UserControl x:Class="RectDynamicGradient.RectangleLevel"
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-namespace:RectDynamicGradient"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="180" d:DesignWidth="80">
<Grid>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Cyan" Offset="{Binding Gradient_top_color, diag:PresentationTraceSources.TraceLevel=High}"/>
<GradientStop Color="Black" Offset="{Binding Gradient_bottom_color, diag:PresentationTraceSources.TraceLevel=High}"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
USERCONTROL CODE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace RectDynamicGradient
{
/// <summary>
/// Interaction logic for RectangleLevel.xaml
/// </summary>
public partial class RectangleLevel : UserControl
{
public double Gradient_bottom_color { get; set; }
public double Gradient_top_color { get; set; }
public double RectLevel
{
get { return (double)GetValue(RectLevelProperty); }
set { SetValue(RectLevelProperty, value); }
}
// Using a DependencyProperty as the backing store for RectLevel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RectLevelProperty =
DependencyProperty.Register("RectLevel", typeof(double), typeof(RectangleLevel), new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(ChangeLevel),
new CoerceValueCallback(CoerceLevel)),
new ValidateValueCallback(ValidateLevel));
public static void ChangeLevel(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RectangleLevel t = d as RectangleLevel;
t.UpdateGradientStops((double)e.NewValue);
}
public static object CoerceLevel(DependencyObject d, object value)
{
if (value is string valstring)
{
if (Double.TryParse(valstring, out double lvl))
{
return lvl;
}
}
if (value is double valdouble)
{
return valdouble;
}
throw new Exception();
}
public static bool ValidateLevel(object value)
{
double? level = 0;
if (value is string valstring)
{
if (Double.TryParse(valstring, out double lvl))
{
level = lvl;
}
}
if (value is double valdouble)
{
level = valdouble;
}
if (level.HasValue && level >= 0 && level <= 1)
return true;
else
return false;
}
public RectangleLevel()
{
InitializeComponent();
this.DataContext = this;
}
private void UpdateGradientStops(double level)
{
double scale = 0;
if (level < .5)
{
scale = level;
}
else if (level >= .5)
{
scale = 1 - level;
}
Gradient_top_color = level;
Gradient_bottom_color = level + (level * .1 * scale);
}
}
}
MAINWINDOW XAML
<Window x:Class="RectDynamicGradient.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:RectDynamicGradient"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Slider x:Name="TheSlider" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" LargeChange="0.1" Maximum="1" SmallChange="0.01" >
<Slider.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="2"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform Y="2"/>
</TransformGroup>
</Slider.RenderTransform>
</Slider>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<TextBlock Text="SliderValue:"/>
<TextBlock HorizontalAlignment="Center" FontSize="32" Text="{Binding ElementName=TheSlider, Path=Value}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<TextBlock Text="UC LEVEL Value:"/>
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="32" Text="{Binding ElementName=MyUserControl, Path=RectLevel}"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center">
<local:RectangleLevel x:Name="MyUserControl" Width="80" VerticalAlignment="Stretch" RectLevel="{Binding ElementName=TheSlider, Path=Value}"/>
</StackPanel>
</Grid>
</Window>
MAINWINDOW CODE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace RectDynamicGradient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
EDIT
After feedback from the comments the improvments were made.
DataContext removed this and added relative source to user control (named "uc")
Changed NotifyProperties into DependencyProperties
Replaced DataContext
<GradientStop Color="Cyan" Offset="{Binding ElementName=uc, Path=GradientFilledColor}"/>
Updated Dependency Properties
/// <summary>
/// Property to change the GradientEmptyColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientEmptyColor
{
get { return (double)GetValue(GradientEmptyColorProperty); }
set { SetValue(GradientEmptyColorProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for GradientEmptyColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientEmptyColorProperty =
DependencyProperty.Register("GradientEmptyColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
/// <summary>
/// Property to change the GradientFilledColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientFilledColor
{
get { return (double)GetValue(GradientFilledColorProperty); }
set { SetValue(GradientFilledColorProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for GradientFilledColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientFilledColorProperty =
DependencyProperty.Register("GradientFilledColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
The DependencyProperty provides the same capability as the INotifyProperty PropertChanged event (uses a different mechanism though).
As mentioned by #Clemens setting datacontext to self is a terrible idea and would break linking when the user control is used through-out the project and datacontext is set to something else.
I am still open to suggestions that help support good practices and learning.
UPDATED CODE
USERCONTROL XAML
<UserControl x:Class="RectDynamicGradient.RectangleLevel"
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-namespace:RectDynamicGradient"
mc:Ignorable="d"
d:DesignHeight="180" d:DesignWidth="80" x:Name="uc">
<Grid>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Rectangle.Fill>
<LinearGradientBrush StartPoint=".5,1" EndPoint=".5,0">
<GradientStop Color="Cyan" Offset="{Binding ElementName=uc, Path=GradientFilledColor}"/>
<GradientStop Color="Black" Offset="{Binding ElementName=uc, Path=GradientEmptyColor}"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
USERCONTROL CODE
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace RectDynamicGradient
{
/// <summary>
/// Interaction logic for RectangleLevel.xaml
/// </summary>
public partial class RectangleLevel : UserControl
{
/// <summary>
/// Property to change the GradientEmptyColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientEmptyColor
{
get { return (double)GetValue(GradientEmptyColorProperty); }
set { SetValue(GradientEmptyColorProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for GradientEmptyColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientEmptyColorProperty =
DependencyProperty.Register("GradientEmptyColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
/// <summary>
/// Property to change the GradientFilledColor (GradientStop offset) of the rectangle by interfacing with the dependency property.
/// </summary>
public double GradientFilledColor
{
get { return (double)GetValue(GradientFilledColorProperty); }
set { SetValue(GradientFilledColorProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for GradientFilledColor. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty GradientFilledColorProperty =
DependencyProperty.Register("GradientFilledColor", typeof(double), typeof(RectangleLevel), new PropertyMetadata(0.0));
/// <summary>
/// Property to change the level (GradientStop offsets) of the rectangle by interfacing with the dependency property.
/// </summary>
public double RectLevel
{
get { return (double)GetValue(RectLevelProperty); }
set { SetValue(RectLevelProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for RectLevel. This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty RectLevelProperty =
DependencyProperty.Register("RectLevel", typeof(double), typeof(RectangleLevel), new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(ChangeLevel),
new CoerceValueCallback(CoerceLevel)),
new ValidateValueCallback(ValidateLevel));
/// <summary>
/// PropertyChangedCallback for DependencyProperty RectLevelProperty.
/// </summary>
/// <param name="d">Dependency object causing the event.</param>
/// <param name="e">Change event arguments.</param>
public static void ChangeLevel(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RectangleLevel t = d as RectangleLevel;
t.UpdateGradientStops((double)e.NewValue);
}
/// <summary>
/// CoerceValueCallback for DependencyProperty RectLevelProperty.
/// </summary>
/// <param name="d">Dependency object causing the event.</param>
/// <param name="value">Value being sent to RectLevelProperty be coerced.</param>
/// <returns></returns>
public static object CoerceLevel(DependencyObject d, object value)
{
if (value is string valstring)
{
if (Double.TryParse(valstring, out double lvl))
{
return lvl;
}
}
if (value is double valdouble)
{
return valdouble;
}
throw new Exception();
}
/// <summary>
/// ValidateValueCallback for DependencyProperty RectLevelProperty
/// </summary>
/// <param name="value">Value being sent to RectLevelProperty be validated.</param>
/// <returns>True, if valid value between and including 0 and 1.</returns>
public static bool ValidateLevel(object value)
{
double? level = 0;
if (value is string valstring)
{
if (Double.TryParse(valstring, out double lvl))
{
level = lvl;
}
}
if (value is double valdouble)
{
level = valdouble;
}
if (level.HasValue && level >= 0 && level <= 1)
return true;
else
return false;
}
/// <summary>
/// Constructor sets DataContext to itself.
/// </summary>
public RectangleLevel()
{
InitializeComponent();
this.DataContext = this;
}
/// <summary>
/// Updates the variables binded to the GradientStops for the rectangle.
/// </summary>
/// <param name="level">Level where the GradientStops should be. Valid value is 0 to 1 representing 0 to 100% filled.</param>
private void UpdateGradientStops(double level)
{
double scale = 0;
if (level < .5)
{
scale = level;
}
else if (level >= .5)
{
scale = 1 - level;
}
GradientFilledColor = level;
GradientEmptyColor = level + (level * .1 * scale);
}
}
}
MAINWINDOW XAML
<Window x:Class="RectDynamicGradient.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:RectDynamicGradient"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Slider x:Name="TheSlider" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Center" LargeChange="0.1" Maximum="1" SmallChange="0.01" >
<Slider.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="2"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform Y="2"/>
</TransformGroup>
</Slider.RenderTransform>
</Slider>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<TextBlock Text="SliderValue:"/>
<TextBlock HorizontalAlignment="Center" FontSize="32" Text="{Binding ElementName=TheSlider, Path=Value}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<TextBlock Text="UC LEVEL Value:"/>
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="32" Text="{Binding ElementName=MyUserControl, Path=RectLevel}"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center">
<local:RectangleLevel x:Name="MyUserControl" Width="80" VerticalAlignment="Stretch" RectLevel="{Binding ElementName=TheSlider, Path=Value}"/>
</StackPanel>
</Grid>
</Window>
Related
I know this question has been posted already, but I don't understand the answers. In My case I have a project with only a UserControl:
<UserControl x:Class="SimpleOxyPlotUserControl.UserControl1View"
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-namespace:SimpleOxyPlotUserControl"
xmlns:oxy="http://oxyplot.org/wpf"
mc:Ignorable="d"
x:Name="uc1"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<oxy:PlotView Model="{Binding ElementName=uc1, Path=OxyPlotModel, Mode=TwoWay}"/>
</Grid>
</UserControl>
and its code behind:
using System.Windows;
using System.Windows.Controls;
using OxyPlot;
namespace SimpleOxyPlotUserControl
{
/// <summary>
/// Interaction logic for UserControl1View.xaml
/// </summary>
public partial class UserControl1View : UserControl
{
public UserControl1View() => InitializeComponent();
public PlotModel OxyPlotModel
{
get { return (PlotModel)GetValue(OxyPlotModelProperty); }
set { SetValue(OxyPlotModelProperty, value); }
}
public static readonly DependencyProperty OxyPlotModelProperty =
DependencyProperty.Register("OxyPlotModel", typeof(PlotModel), typeof(UserControl1View),
new PropertyMetadata(new PlotModel()));
}
}
Then I have another project with a simple WPF App and its MainWindow.xaml:
<Window x:Class="SimpleOxyPlotUserControlApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:simpleOxyPlotUserControl="clr-namespace:SimpleOxyPlotUserControl;assembly=SimpleOxyPlotUserControl"
Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<simpleOxyPlotUserControl:UserControl1View/>
<simpleOxyPlotUserControl:UserControl1View Grid.Row="1"/>
</Grid>
</Window>
and its code behind:
using System.Windows;
namespace SimpleOxyPlotUserControlApp.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
I receive the error message "InvalidOperationException: This PlotModel is already in use by some other PlotView control."
I know this is, because each Oxy PlotModel can only be connected to exactly one Oxy PlotView. But why does it not create a new Oxy PlotView and PlotModel each time, when I insert my UserControl? And how can I ensure that it does so?
OxyPlotModelProperty is registered with new PlotModel() default value in PropertyMetadata. but OxyPlotModelProperty is static so you get the same PlotModel instance for all UserControl1View instances.
fix it by creating PlotModel in constructor:
public partial class UserControl1View : UserControl
{
public UserControl1View()
{
InitializeComponent();
SetLocalValue(OxyPlotModelProperty, new PlotModel());
}
public PlotModel OxyPlotModel
{
get { return (PlotModel)GetValue(OxyPlotModelProperty); }
set { SetValue(OxyPlotModelProperty, value); }
}
public static readonly DependencyProperty OxyPlotModelProperty =
DependencyProperty.Register("OxyPlotModel", typeof(PlotModel), typeof(UserControl1View),
new PropertyMetadata(null));
}
I am writing a wpf application and implements mvvm light tool. The GUI looks like:
Every time when a user click on button, it should change the content on the right side, marked with red border. The XAML code:
<igWpf:XamRibbonWindow x:Class="BackupCustomizing.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:ignore="http://www.ignore.com"
xmlns:ig="http://schemas.infragistics.com/xaml"
xmlns:views="clr-namespace:BackupCustomizing.Views"
xmlns:igWpf="http://schemas.infragistics.com/xaml/wpf"
mc:Ignorable="d ignore"
Height="400"
Width="700"
Title="Backup customizing V0.1"
DataContext="{Binding Main, Source={StaticResource Locator}}" ResizeMode="NoResize">
<igWpf:XamRibbonWindow.Resources>
<DataTemplate DataType="{x:Type views:ServerView}"></DataTemplate>
</igWpf:XamRibbonWindow.Resources>
<ig:ThemeManager.Theme>
<ig:Office2013Theme />
</ig:ThemeManager.Theme>
<igWpf:RibbonWindowContentHost x:Name="_content"
Theme="Office2013"
igWpf:RibbonWindowContentHost.ApplicationAccentColor="#0072C6">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<views:NavigationView Grid.Column="0"/>
<ContentPresenter Content="{Binding}" Grid.Column="1"/>
</Grid>
</igWpf:RibbonWindowContentHost>
</igWpf:XamRibbonWindow>
and the code behind:
using System.Windows;
using BackupCustomizing.ViewModel;
using Infragistics.Themes;
using Infragistics.Windows.Ribbon;
namespace BackupCustomizing
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : XamRibbonWindow
{
/// <summary>
/// Initializes a new instance of the MainWindow class.
/// </summary>
public MainWindow()
{
InitializeComponent();
Closing += (s, e) => ViewModelLocator.Cleanup();
}
}
}
As you can see the code above, I tried with:
<igWpf:XamRibbonWindow.Resources>
<DataTemplate DataType="{x:Type views:ServerView}"></DataTemplate>
</igWpf:XamRibbonWindow.Resources>
and the content presenter:
<ContentPresenter Content="{Binding}" Grid.Column="1"/>
and here I stocked, how to continue?
The ViewModel code:
using BackupCustomizing.Model;
using GalaSoft.MvvmLight;
namespace BackupCustomizing.ViewModel
{
/// <summary>
/// This class contains properties that the main View can data bind to.
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private string _welcomeTitle = string.Empty;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
});
}
}
}
To get you code working in minimum changes
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private string _welcomeTitle = string.Empty;
private ViewModelBase detailsViewModel = null;
public ViewModelBase DetailsViewModel{
get { return detailsViewModel;}
set { detailsViewModel = value; RaisePropertyChanged("DetailsViewModel"); }
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
detailsViewModel = new ServerViewModel(item); //ViewModel for the ServerView
});
}
}
<igWpf:XamRibbonWindow.Resources>
<DataTemplate DataType="{x:Type viewModel:ServerViewModel}">
<views:ServerView />
</DataTemplate>
</igWpf:XamRibbonWindow.Resources>
<ContentPresenter Content="{Binding DetailsViewModel}" Grid.Column="1"/>
There are other techniques to do MVVM, I am just showing the way to do it with the approach you have started with. Problem with this approach is that it will not scale well to large number of views in the ContentPresenter.
That sits by the image. all labels are crowded to the left of the Canvas.I am using some example code for Windows 7 to have an application manipulate images. What I'd like to do is add a label to the bottom of the image. The program generates images on the fly.
Here is the XAML representing the usercontrol for the Picture:
<UserControl x:Class="DocumentHandlingTouch.Picture"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Image Source="{Binding Path=ImagePath}" Stretch="Fill" Width="Auto" Height="Auto" RenderTransformOrigin="0.5, 0.5">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Path=Angle}"></RotateTransform>
<ScaleTransform ScaleX="{Binding Path=ScaleX}" ScaleY="{Binding Path=ScaleY}"></ScaleTransform>
<TranslateTransform X="{Binding Path=X}" Y="{Binding Path=Y}"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
<Label VerticalAlignment="Bottom" x:Name="thelabel"/>
Here is a portion of the picture control:
public partial class Picture : UserControl
{
public Label label;
public Picture()
{
InitializeComponent();
DataContext = this;
label = new Label();
}
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
}
and this is the code that creates the picture:
Picture p = new Picture();
p.ImagePath = path.ToString();
p.label.Content = p.ImagePath;
This is not working for me because it doesn't really create a label where I can set text on it.
Am I going about this wrong?
i have posted the code out on OneDrive(http://1drv.ms/1zQy3Or) in case I am not representing this well enough
As #Ganesh states, it is probably better just to bind to a string.
Just knocked up somthing that might help. There are many ways to do the binding, but this definitely works.
XML
<Window x:Class="WPF.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300"
x:Name="ViewRoot">
<Grid>
<!-- Do image stuff here.-->
<!-- Put label in appropriate position -->
<Label Content="{Binding ElementName=ViewRoot, Path=MyLabel}"></Label>
</Grid>
</Window>
And Code Behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WPF
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
this.MyLabel = "Hello";
}
public string MyLabel
{
get { return (string)GetValue(MyLabelProperty); }
set { SetValue(MyLabelProperty, value); }
}
// Using a DependencyProperty as the backing store for MyLabel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyLabelProperty =
DependencyProperty.Register("MyLabel", typeof(string), typeof(Window2), new PropertyMetadata(""));
}
}
Hope this helps
Whenever I see DataContext = xxxx inside a UserControl, alarm bells go off in my head. Don't do this, please. One of the biggest benefits of WPF/XAML is the separate UI and data layers, and by doing this you are forcing a specific UI-only data layer on a component, which always seems to cause problems in the future because now you can't use any other data with your UserControl.
But I suspect you are not seeing a label on your UI because you haven't actually added it to the UI anywhere.
For example, here's some XAML that places both a TextBlock and Image inside a panel.
<StackPanel>
<Image Source="{Binding ImagePath}" />
<TextBlock Text="{Binding ImagePath}" />
</StackPanel>
If you really wanted, you could create this via code behind too.
As for the bindings, do you really need a custom UserControl for this? It seems like something a Template would be fine for too.
<ItemsControl ItemsSource="{Binding MyCollectionOfStrings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding }" />
<TextBlock Text="{Binding }" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you did really want a UserControl, I would create a DependencyProperty of type string for your ImagePath, and bind that to both your Image and Label properties.
<UserControl x:Class="MyNamespace.MyPictureControl"
x:Name="PictureControl">
<StackPanel>
<Image Source="{Binding ImagePath, ElementName=PictureControl}" />
<TextBlock Text="{Binding ImagePath, ElementName=PictureControl}" />
</StackPanel>
</UserControl>
public partial class MyPictureControl : UserControl
{
public MyPictureControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ImagePathProperty =
DependencyProperty.Register("ImagePath", typeof(string), typeof(MyPictureControl), new PropertyMetadata(null));
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
}
Then anyplace that wants to use this control can either set or bind the ImagePath property
<local:MyImageControl ImagePath="C:\someImage.jpg" />
<local:MyImagecontrol ImagePath="{Binding SomeString}" />
I have tried to create a usercontrol. Please refer the below code.
<UserControl x:Class="DatagridRow_Learning.Picture"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<Image x:Name="image"/>
<TextBlock x:Name="imageName"/>
</StackPanel>
public partial class Picture : UserControl
{
public Picture()
{
InitializeComponent();
}
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
public static readonly DependencyProperty ImagePathProperty =
DependencyProperty.Register("ImagePath", typeof(string), typeof(Picture), new PropertyMetadata(string.Empty, new PropertyChangedCallback(ImagePathCallBack)));
private static void ImagePathCallBack(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
Picture pic = obj as Picture;
pic.image.Source= new BitmapImage(new Uri((string)args.NewValue));
}
public string ImageLabel
{
get { return (string)GetValue(ImageLabelProperty); }
set { SetValue(ImageLabelProperty, value); }
}
public static readonly DependencyProperty ImageLabelProperty =
DependencyProperty.Register("ImageLabel", typeof(string), typeof(Picture), new PropertyMetadata(string.Empty,new PropertyChangedCallback( ImageLabelCallBack)));
private static void ImageLabelCallBack(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
Picture pic = obj as Picture;
pic.imageName.Text= (string)args.NewValue;
}
}
I have an UserControl defined as follows:
<UserControl x:Class="Speaker.View.Controls.Prompt"
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:conv="clr-namespace:Speaker.View.Converters"
mc:Ignorable="d" Height="Auto" Width="300" x:Name="PromptBox">
<UserControl.Resources>
<conv:VisibilityConverter x:Key="VConverter" />
</UserControl.Resources>
<Border Background="White" Padding="10" BorderThickness="1"
BorderBrush="Gray" CornerRadius="10" Height="80"
Visibility="{Binding Path=Show, ElementName=PromptBox,
Converter={StaticResource VConverter}}"
UseLayoutRounding="True">
<Border.Effect>
<DropShadowEffect BlurRadius="20" RenderingBias="Quality" />
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="10" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBox x:Name="InputText" Width="Auto" Height="20"
Text="{Binding Path=InfoText, ElementName=PromptBox, Mode=OneWay}"
Grid.Row="0" BorderThickness="0" Foreground="#FF8D8D8D"
GotFocus="InputText_GotFocus" LostFocus="InputText_LostFocus" />
<Separator Grid.Row="1" />
<Button Content="{Binding Path=ButtonText, ElementName=PromptBox}" Grid.Row="2"
Width="100" Command="{Binding Path=OkCommand, ElementName=PromptBox}" />
</Grid>
</Border>
What I want to do is this:
when the user clicks on the button, I'd like to run some code (obviously :) ) - this control will be used in some other controls / windows, and the code I'd like to run will be different depending on a scenarion. So how do I bind the Command property of this button with some custom command? Example usage:
<ctrls:Prompt Show="{Binding ShouldLogIn}" ButtonText="{Binding LogInText}"
InfoText="{Binding LogInInfo}" OkCommand="what goes here???" Grid.Row="0" Grid.ZIndex="2" />
Also - I follow the MVVM patern, using the MVVMLight fw, so I'd like the solution to follow it as well.
So the question is - How do I bind to the Button.Command from outside of the prompt control?
I would also suggest making a CustomControl, but if you want to use your UserControl you will need to add a DependencyProperty in your code behind.
public partial class Prompt : UserControl
{
private bool _canExecute;
private EventHandler _canExecuteChanged;
/// <summary>
/// DependencyProperty for the OKCommand property.
/// </summary>
public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register("OKCommand", typeof(ICommand), typeof(Prompt), new PropertyMetadata(OnOKCommandChanged));
/// <summary>
/// Gets or sets the command to invoke when the OKButton is pressed.
/// </summary>
public ICommand OKCommand
{
get { return (ICommand)GetValue(OKCommandProperty); }
set { SetValue(OKCommandProperty, value); }
}
/// <summary>
/// Gets a value that becomes the return value of
/// System.Windows.UIElement.IsEnabled in derived classes.
/// </summary>
protected override bool IsEnabledCore
{
get { return base.IsEnabledCore && _canExecute; }
}
// Command dependency property change callback.
private static void OnOKCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Prompt p = (Prompt)d;
p.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
public Prompt()
{
InitializeComponent();
}
// Add the command.
private void AddCommand(ICommand command)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
_canExecuteChanged = handler;
if (command != null)
command.CanExecuteChanged += _canExecuteChanged;
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (OKCommand != null)
_canExecute = OKCommand.CanExecute(null);
CoerceValue(UIElement.IsEnabledProperty);
}
// Add a new command to the Command Property.
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
RemoveCommand(oldCommand);
AddCommand(newCommand);
}
// Remove an old command from the Command Property.
private void RemoveCommand(ICommand command)
{
EventHandler handler = CanExecuteChanged;
command.CanExecuteChanged -= handler;
}
}
After many years of Windows Forms development, i decided to experiment with WPF. In the company i work for, i have built a great amount of software based on the MDI style and i would like to continue to do so when using WPF.
I know that MDI is not "supported" my WPF and i am trying to find a work around this matter. I also know that i can mimic MDI behavior by the WPF tab control but this is not an optimal solution for me.
Users here, are used to have software with an MDI form and many forms underneath that serve as monitoring different tasks, asynchronously, and being always visible.
Is there any way i can achieve this functionality in WPF without Tab or using 3rd party controls and without some kine of WinForms interop ?
One option is to use the following project:
http://wpfmdi.codeplex.com/
Other option is to make something yourself, which allows for interesting stuff relating to multimonitor behaviour etc..
I have built MDI for WPF using functionality of Popup control. I suggest you do not use common win forms way which based on separate controls, but use MVVM for WPF.
So, each MDI window is Popup which wraps a view model. Application itself is base window which hosts view models and operates with windows via view models.
I provide a real life MDI window sample. Be aware that this is not complete sample, but it is enought to demonstrate some basic conceptions. All necessary classes are custom, so no dependency from the third part components:
<DataTemplate DataType="{x:Type vm:Pane}">
<DataTemplate.Resources>
<ControlTemplate x:Key="uiFreePaneTemplate" TargetType="ContentControl">
<Popup
x:Name="PART_DRAG" HorizontalOffset="{Binding X}" VerticalOffset="{Binding Y}"
IsOpen="{Binding IsOpened}" VerticalAlignment="Top" HorizontalAlignment="Left"
AllowsTransparency="True">
<Border
Width="{Binding Width}" Height="{Binding Height}"
BorderThickness="2,2,2,2" Margin="0" CornerRadius="5"
BorderBrush="{StaticResource WindowBackgroundBrush}"
Background="{StaticResource WindowBackgroundBrush}">
<ContentControl Template="{StaticResource Resizer}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--Pane header-->
<Thumb Grid.Row="0" Width="Auto" Height="21">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragDelta">
<mvvm:EventToCommand Command="{Binding DragCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Thumb.Template>
<ControlTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF0C286C" Offset="1"/>
<GradientStop Color="Transparent"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Name}" Grid.Column="0" />
<Button Template="{StaticResource CloseButton}"
Grid.Column="1"
Margin="1,1,3,1" />
</Grid>
</ControlTemplate>
</Thumb.Template>
</Thumb>
<Grid Grid.Row="1" Background="{StaticResource ControlBackgroundBrush}">
<!--Pane content-->
<AdornerDecorator>
<ContentPresenter Content="{TemplateBinding Content}" />
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
IsTabStop="false"/>
</Grid>
</Grid>
</ContentControl>
</Border>
</Popup>
</ControlTemplate>
</DataTemplate.Resources>
<ContentControl x:Name="uiBorder" Content="{Binding Model}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsFree}" Value="True">
<Setter TargetName="uiBorder" Property="ContentControl.Template" Value="{StaticResource uiFreePaneTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
View model:
public class Pane : HideableChildViewModel, IPane
{
private IViewModel _model;
private bool _isFree = true;
private string _name;
private double _coordinateX;
private double _coordinateY;
private double _width = 200.0;
private double _height = 400.0;
private ICommand _closeCommand;
private ICommand _dragCommand;
private ICommand _resizeCommand;
/// <summary>
/// Initializes a new instance of the Pane class.
/// </summary>
/// <param name="parent">The parent view model</param>
/// <param name="parentPropertySelector">Selector of the parent property</param>
/// <param name="model">VM to place within the pane</param>
public Pane(
IViewModel parent,
Expression<Func<object>> parentPropertySelector,
IViewModel model)
: base(parent, parentPropertySelector)
{
this.Model = model;
this._dragCommand = new DragPaneCommand();
this._resizeCommand = new ResizeCommand();
if (model != null && model is ICloseableVM)
{
this._closeCommand = new ClosePaneCommand();
}
else
{
this._closeCommand = new HideCommand();
}
}
#region Properties
/// <summary>
/// Gets or sets VM to place within the pane
/// </summary>
public IViewModel Model
{
get
{
return this._model;
}
set
{
if (this._model != value)
{
this._model = value;
this.RaisePropertyChanged(() => this.Model);
}
}
}
/// <summary>
/// Gets or sets name of the pane
/// </summary>
[LayoutSettings(IsKey = true)]
public string Name
{
get
{
return this._name;
}
set
{
if (this._name != value)
{
this._name = value;
this.RaisePropertyChanged(() => this.Name);
}
}
}
/// <summary>
/// Gets or sets X coordinate
/// </summary>
[LayoutSettings]
public double X
{
get
{
return this._coordinateX;
}
set
{
if (this._coordinateX != value)
{
this._coordinateX = value;
this.RaisePropertyChanged(() => this.X);
}
}
}
/// <summary>
/// Gets or sets Y coordinate
/// </summary>
[LayoutSettings]
public double Y
{
get
{
return this._coordinateY;
}
set
{
if (this._coordinateY != value)
{
this._coordinateY = value;
this.RaisePropertyChanged(() => this.Y);
}
}
}
/// <summary>
/// Gets or sets width
/// </summary>
[LayoutSettings]
public double Width
{
get
{
return this._width;
}
set
{
if (this._width != value)
{
this._width = value;
this.RaisePropertyChanged(() => this.Width);
}
}
}
/// <summary>
/// Gets or sets height
/// </summary>
[LayoutSettings]
public double Height
{
get
{
return this._height;
}
set
{
if (this._height != value)
{
this._height = value;
this.RaisePropertyChanged(() => this.Height);
}
}
}
/// <summary>
/// Gets or sets a value indicating whether pane is free
/// </summary>
public bool IsFree
{
get
{
return this._isFree;
}
set
{
if (this._isFree != value)
{
this._isFree = value;
this.OnIsFreeChanged(this._isFree);
this.RaisePropertyChanged(() => this.IsFree);
}
}
}
#endregion
#region Commands
/// <summary>
/// Gets command for pane closing
/// </summary>
public ICommand CloseCommand
{
get { return this._closeCommand; }
}
/// <summary>
/// Gets command for pane dragging
/// </summary>
public ICommand DragCommand
{
get { return this._dragCommand; }
}
/// <summary>
/// Gets command for pane resize
/// </summary>
public ICommand ResizeCommand
{
get { return this._resizeCommand; }
}
#endregion
private void OnIsFreeChanged(bool isFree)
{
if (!isFree)
{
return;
}
IDockContainer oContainer = ((IChildViewModel)((IChildViewModel)this.Parent).Parent).Parent as IDockContainer;
if (oContainer != null)
{
this.SetParent(oContainer, () => oContainer.FreeItems);
}
}
}