The small example of work on animation, does not work variation of property of model, at animation
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="440" Width="732" Loaded="Window_Loaded"
MouseLeftButtonDown="Grid_MouseLeftButtonDown" MouseLeftButtonUp="Grid_MouseLeftButtonUp" MouseMove="Grid_MouseMove">
<Window.Resources>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Canvas Name="canvMain" Height="360" Width="710" Grid.Row="1">
<Rectangle Name="Zombi" Width="20" Height="40" Stroke="Black" StrokeThickness="2" Canvas.Bottom="0" Canvas.Right="{Binding Path=X, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True}"></Rectangle>
</Canvas>
</Grid>
public double X
{
get { return _x; }
set
{
if (!double.IsNaN(value))
{
_x = value;
OnPropertyChanged("X");
}
}
}
double pixelMetr = 0.715;
private int GetPixel(double metr)
{
return Convert.ToInt32(metr / pixelMetr);
}
private void StartZombi()
{
double distance = 250;
double maxTime = (new Random()).Next(5, 10);
PathGeometry animationPath = new PathGeometry();
LineGeometry lineGeometry = new LineGeometry();
lineGeometry.StartPoint = new Point(0, 0);
lineGeometry.EndPoint = new Point(GetPixel(distance), 0);
animationPath.AddGeometry(lineGeometry);
DoubleAnimationUsingPath animationX = new DoubleAnimationUsingPath();
animationX.PathGeometry = animationPath;
animationX.Duration = TimeSpan.FromSeconds(maxTime);
animationX.Source = PathAnimationSource.X;
Storyboard.SetTarget(animationX, Zombi);
Storyboard.SetTargetProperty(animationX, new PropertyPath(Canvas.RightProperty));
Storyboard pathAnimationStoryboard = new Storyboard();
pathAnimationStoryboard.RepeatBehavior = RepeatBehavior.Forever;
pathAnimationStoryboard.Children.Add(animationX);
pathAnimationStoryboard.Begin(this);
}
Hello why does not work property change, I need to get the current value of the property ' canvas.right ', when the animation runs.
This is xaml code.
OK, I found you a solution... all you need to do is to create a DependencyProperty for X and animate that instead:
public static readonly DependencyProperty XProperty = DependencyProperty.
Register("X", typeof(double), typeof(MainWindow));
public double X
{
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
Then your animation method:
private void StartZombi()
{
double distance = 250;
double maxTime = (new Random()).Next(5, 10);
PathGeometry animationPath = new PathGeometry();
LineGeometry lineGeometry = new LineGeometry();
lineGeometry.StartPoint = new Point(0, 0);
lineGeometry.EndPoint = new Point(GetPixel(distance), 0);
animationPath.AddGeometry(lineGeometry);
DoubleAnimationUsingPath animationX = new DoubleAnimationUsingPath();
animationX.PathGeometry = animationPath;
animationX.Duration = TimeSpan.FromSeconds(maxTime);
animationX.Source = PathAnimationSource.X;
Storyboard.SetTarget(animationX, This); // <<< 'This' is the Window.Name
Storyboard.SetTargetProperty(animationX,
new PropertyPath(MainWindow.XProperty));
Storyboard pathAnimationStoryboard = new Storyboard();
pathAnimationStoryboard.RepeatBehavior = RepeatBehavior.Forever;
pathAnimationStoryboard.Children.Add(animationX);
pathAnimationStoryboard.Begin(this);
}
And the XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Canvas Name="canvMain" Height="360" Width="710" Grid.Row="1">
<Rectangle Name="Zombi" Width="20" Height="40" Stroke="Black"
StrokeThickness="2" Canvas.Bottom="60" Canvas.Right="{Binding Path=X,
UpdateSourceTrigger=PropertyChanged}" />
</Canvas>
<TextBlock Grid.Row="0" FontSize="26" Text="{Binding X}" />
</Grid>
The last thing that you need to do to make this work is to set the Window.Name property to "This".
To answer your original question as to why your code was not working, I'm not 100% sure, but I believe that it was because the Canvas.X property is not plugged into the INotifyPropertyChanged interface and so your original CLR X property setter was never being called. There is a similar situation when Binding to the Count property of a collection... it won't update the UI when you add items to the collection, as it is not notifying the changes.
Related
I implemented the code in the first answer to this question and it works great except that it doesn't include the title bar, which is a must for my needs. Does anybody have any idea what I'm doing wrong?
I've updated my example to show all the other commented out attempts that failed.
Here's my code:
Window shellView = Application.Current.MainWindow;
Rect bounds = VisualTreeHelper.GetDescendantBounds(shellView);
//Rect bounds = new Rect(new Size(shellView.ActualWidth, shellView.ActualHeight));
//Rect bounds = shellView.RestoreBounds;
//Rect bounds = VisualTreeHelper.GetContentBounds(shellView);
//Rect bounds = new Rect(new Size(VisualTreeHelperEx.FindDescendantByType<Window>(shellView).ActualWidth,
// VisualTreeHelperEx.FindDescendantByType<Window>(shellView).ActualHeight));
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
string fileName = $#"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\Screen_Capture.png";
if (_dialogService.ShowSaveFileDialog(ref fileName, "PNG Files | *.png"))
{
using (DrawingContext context = visual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(shellView);
context.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderTarget.Render(visual);
PngBitmapEncoder bitmapEncoder = new PngBitmapEncoder();
bitmapEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (Stream stm = File.Create(fileName))
{
bitmapEncoder.Save(stm);
}
}
And at XAMIMAX's request, what I can share of the XAML with names changed:
<Window x:Class="MyProject.Shell.Views.Shell.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ap="clr-namespace:MyProject.Common.Support.AttachedProperties;assembly=MyProject.Common.Support"
xmlns:controls="clr-namespace:MyProject.Common.Support.Controls;assembly=MyProject.Common.Support"
xmlns:converters="clr-namespace:MyProject.Common.Support.Converters;assembly=MyProject.Common.Support"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dts="clr-namespace:MyProject.Common.Support.DataTemplateSelectors;assembly=MyProject.Common.Support"
xmlns:enums="clr-namespace:MyProject.Shell.Enums"
xmlns:enums1="clr-namespace:MyProject.Common.Support.Enums;assembly=MyProject.Common.Support"
xmlns:ikriv="clr-namespace:MyProject.Common.Support.AttachedProperties;assembly=MyProject.Common.Support"
xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:m="clr-namespace:MyProject.Common.Support.MarkupExtensions;assembly=MyProject.Common.Support"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resources="clr-namespace:MyProject.Shell.Views.Shell.Resources"
xmlns:ss="clr-namespace:MyProject.Common.Support.StyleSelectors;assembly=MyProject.Common.Support"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:views="clr-namespace:MyProject.NotificationModule.Client.Views;assembly=MyProject.NotificationModule.Client"
xmlns:constants="clr-namespace:MyProject.Shell"
x:Name="shell"
Title="{Binding MyProjectApplicationTitle}"
Width="1024"
Height="768"
MinWidth="800"
MinHeight="600"
Background="{DynamicResource PrimarySolidColorBrush}"
Icon="/Resources/Embedded/MyProject.ico"
mc:Ignorable="d">
<controls:LocalizationScope.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Children>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding NavigationBarWidth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth="38"
MaxWidth="400" />
</Grid>
</Grid.Children>
</Grid>
</controls:LocalizationScope.Content>
</Window>
I hope that's enough of the XAML to help. I also tried the example at this link Generating a screenshot of a WPF window, but got the same results: no title bar.
Thanks to the link that #Clemens sent me to, I was able to use some of the code on that page to come up with this working method. It grabs a few pixels more that the active window, but that works for me!
private void TakeScreenshot()
{
var rect = new Rect();
GetWindowRect(GetForegroundWindow(), ref rect);
var bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
var result = new Bitmap(bounds.Width, bounds.Height);
using (var graphics = Graphics.FromImage(result))
{
graphics.CopyFromScreen(new System.Drawing.Point(bounds.Left, bounds.Top), System.Drawing.Point.Empty, bounds.Size);
}
string fileName = $#"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\Screen_Capture.png";
if (_dialogService.ShowSaveFileDialog(ref fileName, "PNG Files | *.png"))
{
result.Save(fileName, ImageFormat.Png);
_dialogService.ShowInfoDialog(_localizationService["Shell.Views.Shell.HelpMenu.ScreenshotSaved"] + $"{fileName}");
}
}
I working on a simple imageviewer app. I control the Stretch property on the binding based on ViewModel property.
The problem occurs when I change the Stretch attribute based on a 'Combobox', bound to ViewModel, and the image 'cuts off' the corners of a wide image when using 'UniformToFill'. Hence to use of a ScrollViewer to be able to scroll the image content.
The problem is the ScrollViewer doesn't seem to show up scrollbars for me to be able to scroll.
WPF Markup:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Other Grids removed -->
<Grid Name="Container" Grid.Column="2" Grid.Row="0" Grid.RowSpan="2">
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Image Source="{Binding SelectedPhoto.Value.Image}"
Stretch="{Binding ImageStretch}" Name="PhotoImage" />
</ScrollViewer>
</Grid>
I understand if I set a fixed Height and Width to ScrollViewer and Image, it will work. But I want to do it Dynamically:
The ScrollView Will have Height and Width from Parent 'Grid(Contaioner)' Control.
The Image will have Height and Width from itself, but take Stretch to account in that calculation.
Possible to solve with ActualHeight, ActualWidth? And a DependecyProperty?
This is almost impossible, Or I should say it doesn't make a lot of sense to expect ScrollViewer to know the boundaries of an image with Stretch = UniformToFill. According to MSDN:
UniformToFill:
The content (your Image) is resized to fill the destination dimensions (window or grid) while it
preserves its native aspect ratio. If the aspect ratio of the
destination rectangle differs from the source, the source content is
clipped to fit in the destination dimensions (Therefore the image will be cutted off).
So I think what we really need here is to use Uniform + Proper Scaling instead of UniformToFill.
The solution is when Stretch is set to UniformToFill it must set to Uniform and then Image.Width = image actual width * scalingParam and Image.Height= image actual height * scalingParam, where scalingParam = Grid.Width (or Height) / image actual width (or Height). This way ScrollViewer boundaries will be the same as the image scaled size.
I've provided a working solution to give you an Idea, I'm not sure how suitable would it be for your case but here it is:
First I defined a simple view-model for my Images:
public class ImageViewModel: INotifyPropertyChanged
{
// implementation of INotifyPropertyChanged ...
private BitmapFrame _bitmapFrame;
public ImageViewModel(string path, Stretch stretch)
{
// determining the actual size of the image.
_bitmapFrame = BitmapFrame.Create(new Uri(path), BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
Width = _bitmapFrame.PixelWidth;
Height = _bitmapFrame.PixelHeight;
Scale = 1;
Stretch = stretch;
}
public int Width { get; set; }
public int Height { get; set; }
double _scale;
public double Scale
{
get
{
return _scale;
}
set
{
_scale = value;
OnPropertyChanged("Scale");
}
}
Stretch _stretch;
public Stretch Stretch
{
get
{
return _stretch;
}
set
{
_stretch = value;
OnPropertyChanged("Stretch");
}
}
}
In the above code BitmapFrame is used to determine the actual size of the image.
Then I did some initializations in my Mainwindow (or main view-model):
// currently displaying image
ImageViewModel _imageVm;
public ImageViewModel ImageVM
{
get
{
return _imageVm;
}
set
{
_imageVm = value;
OnPropertyChanged("ImageVM");
}
}
// currently selected stretch type
Stretch _stretch;
public Stretch CurrentStretch
{
get
{
return _stretch;
}
set
{
_stretch = value;
//ImageVM should be notified to refresh UI bindings
ImageVM.Stretch = _stretch;
OnPropertyChanged("ImageVM");
OnPropertyChanged("CurrentStretch");
}
}
// a list of Stretch types
public List<Stretch> StretchList { get; set; }
public string ImagePath { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
// sample image path
ImagePath = #"C:\Users\...\YourFile.png";
StretchList = new List<Stretch>();
StretchList.Add( Stretch.None);
StretchList.Add( Stretch.Fill);
StretchList.Add( Stretch.Uniform);
StretchList.Add( Stretch.UniformToFill);
ImageVM = new ImageViewModel(ImagePath, Stretch.None);
CurrentStretch = StretchList[0];
}
My Xaml looks like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.Resources>
<local:MultiConverter x:Key="multiC"/>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Image Source="{Binding ImagePath}" Name="PhotoImage">
<Image.Stretch>
<MultiBinding Converter="{StaticResource multiC}">
<Binding Path="ImageVM" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualWidth"/>
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualHeight"/>
</MultiBinding>
</Image.Stretch>
<Image.LayoutTransform>
<ScaleTransform ScaleX="{Binding ImageVM.Scale}" ScaleY="{Binding ImageVM.Scale}"
CenterX="0.5" CenterY="0.5" />
</Image.LayoutTransform>
</Image>
</ScrollViewer>
</Grid>
<ComboBox Grid.Row="2" Grid.Column="0" ItemsSource="{Binding StretchList}" SelectedItem="{Binding CurrentStretch}" DisplayMemberPath="."/>
</Grid>
As you can see, I've used a multi-value converter that takes 3 arguments: current image view-model and window width and height. This arguments were used to calculate current size of the area that image fills. Also I've used ScaleTransform to scale that area to the calculated size. This is the code for multi-value converter:
public class MultiConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is ImageViewModel)
{
var imageVm = (ImageViewModel)values[0];
// if user selects UniformToFill
if (imageVm.Stretch == Stretch.UniformToFill)
{
var windowWidth = (double)values[1];
var windowHeight = (double)values[2];
var scaleX = windowWidth / (double)imageVm.Width;
var scaleY = windowHeight / (double)imageVm.Height;
// since it's "uniform" Max(scaleX, scaleY) is used for scaling in both horizontal and vertical directions
imageVm.Scale = Math.Max(scaleX, scaleY);
// "UniformToFill" is actually "Uniform + Proper Scaling"
return Stretch.Uniform;
}
// if user selects other stretch types
// remove scaling
imageVm.Scale = 1;
return imageVm.Stretch;
}
return Binding.DoNothing;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
So ultimately i took a discussion with some co-workers and we agreed that we need to fix the problem before a fix. In other words replace Stretch attribute combined with scrollviewer with something more robust that will support extent ability.
The solution I came up with will work for now, and a better solution to the whole problem will be preformed next scrum sprint.
Solution
A custom dependencyproperty that will control width and height depending on stretch attribute currently present on element.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Column="2" Grid.Row="0" Grid.RowSpan="2">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Image Name="PhotoImage"
Source="{Binding SelectedPhoto.Value.Image}"
Stretch="{Binding ImageStretch, NotifyOnTargetUpdated=True}}"
extensions:ImageExtensions.ChangeWidthHeightDynamically="True"/>
</ScrollViewer>
</Grid>
Dependency Property
public static bool GetChangeWidthHeightDynamically(DependencyObject obj)
{
return (bool)obj.GetValue(ChangeWidthHeightDynamicallyProperty);
}
public static void SetChangeWidthHeightDynamically(DependencyObject obj, bool value)
{
obj.SetValue(ChangeWidthHeightDynamicallyProperty, value);
}
public static readonly DependencyProperty ChangeWidthHeightDynamicallyProperty =
DependencyProperty.RegisterAttached("ChangeWidthHeightDynamically", typeof(bool), typeof(ImageExtensions), new PropertyMetadata(false, OnChangeWidthHeightDynamically));
private static void OnChangeWidthHeightDynamically(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var image = d as Image;
if (image == null)
return;
image.SizeChanged += Image_SizeChanged;
image.TargetUpdated += Updated;
}
private static void Updated(object sender, DataTransferEventArgs e)
{
//Reset Width and Height attribute to Auto when Target updates
Image image = sender as Image;
if (image == null)
return;
image.Width = double.NaN;
image.Height = double.NaN;
}
private static void Image_SizeChanged(object sender, SizeChangedEventArgs e)
{
var image = sender as Image;
if (image == null)
return;
image.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
if (Math.Abs(image.ActualHeight) <= 0 || Math.Abs(image.ActualWidth) <= 0)
return;
switch (image.Stretch)
{
case Stretch.Uniform:
{
image.Width = Double.NaN;
image.Height = Double.NaN;
break;
}
case Stretch.None:
{
image.Width = image.RenderSize.Width;
image.Height = image.RenderSize.Height;
break;
}
case Stretch.UniformToFill:
{
image.Width = image.ActualWidth;
image.Height = image.ActualHeight;
break;
}
default:
{
image.Width = double.NaN;
image.Height = double.NaN;
break;
}
}
}
The problem may come from the rest of your layout - If the Grid is contained in an infinitely resizable container (a Grid Column/Row set to Auto, a StackPanel, another ScrollViewer...), it will grow with the Image. And so will do the ScrollViewer, instead of activating the scroll bars.
I'm organizing my grid with RowDefinitions and ColumnDefinition, but forever when I want add a new RowDefinition in before actual any RowDefinition, I need reorganize Grid.Row of all controls
I saw RowDefinition and ColumnDefinition has a Name property, so I think is possible define Grid.Row with RowDefinition name or not? If is possible, How do
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="RowDictTitle" Height="27"/>
<RowDefinition Name="RowSearchWord" Height="27"/>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<!--Row 1-->
<TextBlock Text="Word:" VerticalAlignment="Center" Margin="10,0,0,0" Grid.Row="1"/>
<TextBox Name="Search" Grid.ColumnSpan="2" Margin="50,2,10,2"/>
<!--Row 2-->
<ListBox Name="Words" Grid.Row="2" Margin="10"/>
</Grid>
I want make below
<TextBlock Text="Word" Grid.Row="RowSearchWord"/>
Disclaimer: This answer is kind of a self-advertisement within the constraints alluded to by this meta post. It advertises a free open source project that I (at the time of writing this) do not earn any money with. The only gain is the knowledge that my time for writing the described control was not wasted if it helps some future visitors of this SO question.
I had exactly the same thoughts. That is why, not too long ago, I wrote a custom grid class that uses named columns and rows.
I put it on Codeplex under the MIT license: Name-Based Grid project
With that control, you can rewrite your Xaml source code as follows:
<nbg:NameBasedGrid>
<nbg:NameBasedGrid.RowDefinitions>
<nbg:ColumnOrRow Name="RowDictTitle" Height="27"/>
<nbg:ColumnOrRow Name="RowSearchWord" Height="27"/>
<nbg:ColumnOrRow Name="List"/>
<nbg:ColumnOrRow Height="50"/>
</nbg:NameBasedGrid.RowDefinitions>
<nbg:NameBasedGrid.ColumnDefinitions>
<nbg:ColumnOrRow Width="1*" Name="Left"/>
<nbg:ColumnOrRow Width="2*" Name="Right"/>
</nbg:NameBasedGrid.ColumnDefinitions>
<!--Row 1-->
<TextBlock Text="Word:" VerticalAlignment="Center" Margin="10,0,0,0" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="RowSearchWord"/>
<TextBox Name="Search" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="RowDictTitle" nbg:NameBasedGrid.ExtendToColumn="Right" Margin="50,2,10,2"/>
<!--Row 2-->
<ListBox Name="Words" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="List" Margin="10"/>
</nbg:NameBasedGrid>
Advantage: You will be able to reference columns and rows (including column and row spans!) by name - no more counting of columns or rows, no more updating column or row spans when the layout changes.
Disadvantage: You will need to explicitly state names for all columns and rows, as numerical references are not supported at all in NameBasedGrid.
Nice idea but since the Grid.Row attached property is an integer this is not possible.
See http://msdn.microsoft.com/en-us/library/system.windows.controls.grid.row.aspx
However, it may possible to create a helper that takes the name of the grid row, finds the row object and returns its row index.
I was looking for the same thing. Could not find exacly what I was looking for so i came up with my own solution using attached properties.
I created a specialized grid with attached properties for RowName and ColumnName.
(In this example i only implemented RowName)
using System.Windows;
using System.Windows.Controls;
namespace GridNamedRows.CustomControl
{
public class MyGrid: Grid
{
public static readonly DependencyProperty RowNameProperty =
DependencyProperty.RegisterAttached(
"RowName",
typeof(string),
typeof(MyGrid),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsParentArrange,
new PropertyChangedCallback(RowNameChanged)),
new ValidateValueCallback(IsStringNotNull));
private static bool IsStringNotNull(object value)
{
return (value as string) != null;
}
private static void RowNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
return;
}
if (!(d is UIElement)) return;
Grid parent = ((FrameworkElement)d).Parent as Grid;
if (parent == null) return;
//Find rowname
for (int i = 0; i < parent.RowDefinitions.Count; i++)
{
if (parent.RowDefinitions[i].Name == e.NewValue.ToString())
{
Grid.SetRow((UIElement)d, i);
break;
}
}
}
public static string GetRowName(DependencyObject target)
{
return (string)target.GetValue(RowNameProperty);
}
public static void SetRowName(DependencyObject target, string value)
{
target.SetValue(RowNameProperty, value);
}
}
}
It can be used in xaml like this.
<Window xmlns:CustomControl="clr-namespace:GridNamedRows.CustomControl" x:Class="GridNamedRows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<CustomControl:MyGrid>
<Grid.RowDefinitions>
<RowDefinition Name="firstRow"/>
<RowDefinition Name="secondRow"/>
<RowDefinition Name="thirdRow"/>
</Grid.RowDefinitions>
<TextBox Text="one" CustomControl:MyGrid.RowName="secondRow"/>
<TextBox Text="two" Grid.Row="2"/>
<TextBox Text="three" CustomControl:MyGrid.RowName="firstRow"/>
</CustomControl:MyGrid>
</Window>
It does not display correctly in the designer but works in runtime.
Along the lines of the other answers I came up with this attached property solution that does not require using a custom Grid.
The code is largely redundant (for row & column) and can be used like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition x:Name="ThisRow"/>
<RowDefinition x:Name="ThatRow"/>
<RowDefinition x:Name="AnotherRow"/>
</Grid.RowDefinitions>
<TextBlock helpers:GridHelper.RowName="ThisRow" Text="..."/>
<TextBlock helpers:GridHelper.RowName="AnotherRow" Text="..."/>
<TextBlock helpers:GridHelper.RowName="ThatRow" Text="..."/>
</Grid>
GridHelper.cs:
public class GridHelper
{
public static string GetRowName(DependencyObject obj)
{
return (string)obj.GetValue(RowNameProperty);
}
public static void SetRowName(DependencyObject obj, string value)
{
obj.SetValue(RowNameProperty, value);
}
public static readonly DependencyProperty RowNameProperty =
DependencyProperty.RegisterAttached("RowName", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata(string.Empty, GridHelper.OnRowNamePropertyChanged));
public static void OnRowNamePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var name = e.NewValue?.ToString();
if (string.IsNullOrEmpty(name)) return;
if (!(sender is FrameworkElement fe)) return;
if (!(fe.Parent is Grid grid)) return;
for (int i = 0; i < grid.RowDefinitions.Count; i++)
{
var rd = grid.RowDefinitions[i];
if (rd.Name.Equals(name))
{
Grid.SetRow(fe, i);
return;
}
}
throw new ArgumentException("Invalid RowName: " + name);
}
public static string GetColumnName(DependencyObject obj)
{
return (string)obj.GetValue(ColumnNameProperty);
}
public static void SetColumnName(DependencyObject obj, string value)
{
obj.SetValue(ColumnNameProperty, value);
}
public static readonly DependencyProperty ColumnNameProperty =
DependencyProperty.RegisterAttached("ColumnName", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata(string.Empty, GridHelper.OnColumnNamePropertyChanged));
public static void OnColumnNamePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var name = e.NewValue?.ToString();
if (string.IsNullOrEmpty(name)) return;
if (!(sender is FrameworkElement fe)) return;
if (!(fe.Parent is Grid grid)) return;
for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
{
var cd = grid.ColumnDefinitions[i];
if (cd.Name.Equals(name))
{
Grid.SetColumn(fe, i);
return;
}
}
throw new ArgumentException("Invalid ColumnName: " + name);
}
}
Note: This also may not work in the designer - I've never tried using it...
I have a custom control which shows some statistic data and need always be placed at the top edge of WP7 screen. but, when user inputs something on a textbox, the soft-keyboard popup. And the custom control is moved out of the screen. I want to make sure the custom control always visible, even when soft keyboard is popup. Does anyone know how to do this?
You must to use some "magic". By "magic" I mean RenderTransform.
Solution is simple - you need to move your custom control (down, when keyboard visible; up, when hidden). Check this valuable post - it's must help you.
Regards.
<phone:PhoneApplicationPage
x:Class="Test.Keyboard.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="PortraitOrLandscape"
>
<Grid x:Name="LayoutRoot" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="WINDOWS PHONE" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="developer's ?" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<Grid Grid.Row="1" Margin="12,0,12,0"></Grid>
<TextBox Grid.Row="2" LostFocus="TextBoxLostFocus"/>
</Grid>
</phone:PhoneApplicationPage>
public partial class MainPage : PhoneApplicationPage
{
private const double LandscapeShift = -259d;
private const double LandscapeShiftWithBar = -328d;
private const double Epsilon = 0.00000001d;
private const double PortraitShift = -339d;
private const double PortraitShiftWithBar = -408d;
public static readonly DependencyProperty TranslateYProperty = DependencyProperty.Register("TranslateY", typeof(double), typeof(MainPage), new PropertyMetadata(0d, OnRenderXPropertyChanged));
public MainPage()
{
InitializeComponent();
Loaded += MainPageLoaded;
}
public double TranslateY
{
get { return (double)GetValue(TranslateYProperty); }
set { SetValue(TranslateYProperty, value); }
}
private static void OnRenderXPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MainPage)d).UpdateTopMargin((double)e.NewValue);
}
private void MainPageLoaded(object sender, RoutedEventArgs e)
{
BindToKeyboardFocus();
}
private void BindToKeyboardFocus()
{
PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
if (frame != null)
{
var group = frame.RenderTransform as TransformGroup;
if (group != null)
{
var translate = group.Children[0] as TranslateTransform;
var translateYBinding = new Binding("Y");
translateYBinding.Source = translate;
SetBinding(TranslateYProperty, translateYBinding);
}
}
}
private void UpdateTopMargin(double translateY)
{
if (IsClose(translateY, LandscapeShift) || IsClose(translateY, PortraitShift)
||IsClose(translateY, LandscapeShiftWithBar) || IsClose(translateY, PortraitShiftWithBar)
)
{
LayoutRoot.Margin = new Thickness(0, -translateY, 0, 0);
}
}
private bool IsClose(double a, double b)
{
return Math.Abs(a - b) < Epsilon;
}
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
LayoutRoot.Margin = new Thickness();
}
}
Good Luck....
I want to have a user control[say UC1] comprising 4 text boxes [say tb1,tb2,tb3, and tb4]. This user control should have 4 normal properties [say prop1, prop2, prop3, and prop4] binding to these text boxes. I want a dependency property [say dp] exposed to outer world by this user control.
This user control gets a single string [say 0\abc|1\def|2\ghi|3\jkl] from a property[say StrProp] of class [say C1] and is splitted into 4 parts[say abc, def, ghi, and jkl] to display in 4 text boxes of my user control. If any changes done by user in any or all textboxes, all the changed texts should be combined and reflected back to class C1\StrProp property.
Also, my requirement is that dp should be bounded to StrProp in UI\XAML. Validations should also be done properly.
Can anyone please help me by writing an example?
Sample classes are as below:
MyMultiTextBoxUserControl.xaml
<UserControl x:Class="MyMultiTextBoxControl_UsingNConsuming.MyMultiTextBoxUserControl"
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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=".25*"/>
<RowDefinition Height=".25*"/>
<RowDefinition Height=".25*"/>
<RowDefinition Height=".25*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding ElementName=UserControl, Path=CombinedField1 }"/>
<TextBox Grid.Row="1" Text="{Binding ElementName=UserControl, Path=CombinedField2}"/>
<TextBox Grid.Row="2" Text="{Binding ElementName=UserControl, Path=CombinedField3}"/>
<TextBox Grid.Row="3" Text="{Binding ElementName=UserControl, Path=CombinedField4}"/>
</Grid>
</UserControl>
MyMultiTextBoxUserControl.xaml.cs
public partial class MyMultiTextBoxUserControl : UserControl
{
public MyMultiTextBoxUserControl()
{
InitializeComponent();
}
//static FrameworkPropertyMetadata propertydata = new FrameworkPropertyMetadata("Hello",
// FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(PropertyChanged_Callback), new CoerceValueCallback(CoerceValue_Callback),
// false, UpdateSourceTrigger.LostFocus);
//public static readonly DependencyProperty CombinedTextProperty =
// DependencyProperty.Register("CombinedText", typeof(string), typeof(MyMultiTextBoxUserControl), propertydata, new ValidateValueCallback(Validate_ValueCallback));
static FrameworkPropertyMetadata propertydata = new FrameworkPropertyMetadata("Hello",
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(PropertyChanged_Callback));
public static readonly DependencyProperty CombinedTextProperty =
DependencyProperty.Register("CombinedText", typeof(string), typeof(MyMultiTextBoxUserControl), propertydata);
private static bool Validate_ValueCallback(object value)
{
string str=value as string;
bool result = true;
if (str.Length > 28)
result = false;
if (str.Length < 1)
result = false;
if (str.Substring(0, 2) != "0'\'")
result = false;
if (str.Contains("1'\'") == false || str.Contains("2'\'") || str.Contains("3'\'"))
result = false;
return result;
}
private static object CoerceValue_Callback(DependencyObject obj,object value)
{
return value;
}
private static void PropertyChanged_Callback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyMultiTextBoxUserControl control=(MyMultiTextBoxUserControl)obj;
string select = e.NewValue.ToString();
char[] pipeDelim,slashDelim;
string[] pipeSplt;
pipeDelim = new char[] { '|' };
slashDelim = new Char[] { '/' };
pipeSplt = select.Split(pipeDelim);
if (pipeSplt.Length == 1)
return;
string[][] str = new string[4][];
int x = 0;
foreach (string s in pipeSplt)
{
if (string.IsNullOrEmpty(s) == false)
{
str[x] = s.Split(slashDelim);
x++;
}
}
control.CombinedField1 = str[0][1];
control.CombinedField2 = str[1][1];
control.CombinedField3 = str[2][1];
control.CombinedField4 = str[3][1];
}
public string CombinedText
{
get { return GetValue(CombinedTextProperty) as string; }
set { SetValue(CombinedTextProperty, value); }
}
public string CombinedField1
{
get; set;
}
public string CombinedField2
{
get;
set;
}
public string CombinedField3
{
get;
set;
}
public string CombinedField4
{
get;
set;
}
}
CombinedStringClass.cs
namespace MyMultiTextBoxControl_UsingNConsuming
{
public class CombinedStringClass
{
public CombinedStringClass() { }
string m_CombinedString;
public string CombinedString
{
get { return m_CombinedString; }
set
{
if (m_CombinedString != value)
m_CombinedString = value;
}
}
}
}
ConsumerClass.xaml
<Window x:Class="MyMultiTextBoxControl_UsingNConsuming.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyMultiTextBoxControl_UsingNConsuming;assembly="
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:CombinedStringClass x:Key="myClass"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.33*"/>
<RowDefinition Height="0.34*"/>
<RowDefinition Height="0.33*"/>
</Grid.RowDefinitions>
<TextBlock Text="User Control Text Boxes" Grid.Row="0" Grid.Column="0" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<local:MyMultiTextBoxUserControl Grid.Row="0" Grid.Column="1" Foreground="Black" CombinedText="{Binding Source=myClass, Path=CombinedString, Mode=TwoWay,FallbackValue=DataNotBound}"/>
<TextBlock Text="Combied String" Grid.Row="2" Grid.Column="0" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Name="OneStringTextBox" Grid.Row="2" Grid.Column="1" Foreground="Black" Text="0\abc|1\def|2\ghi|3\jkl" IsEnabled="False"/>
</Grid>
</Window>
I also need to combine the changed texts ofUserControl's textboxes in such a way that it should be in a form of [0\f|1\gh|2\zx|3\oo] to be reflected in OneStringTextBox. Also, total string length should be 28 & max length of each textbox is 7.
Read WPF in C# 2010: Windows Presentation Foundation in .NET 4 Matthew MacDonald Chapter 18.
There is a great example that shoud help you.
Give name to your User control, replace {Binding ElementName=UserControl... with {Binding ElementName=NameOfUserControl, convert CombinedFields properties to DPs.