I've a problem in WPF. I'm making a delete button with an image in it. When the button is disabled, however, I wanted to display a greyscale image.
I found the Thomas Lebrun implementation, but I don't want to add the whole class in my program. Instead I tried to mimic the behavior in this way:
BitmapImage img_Delete = new System.Windows.Media.Imaging.BitmapImage(new Uri("the png URI", UriKind.RelativeOrAbsolute));
ImageSource img_DeleteDisabled = null;
[...]
Button btDel = new Button() { Width = 20, Height = 20, ToolTip = "Delete", Margin = new Thickness(5), HorizontalAlignment = System.Windows.HorizontalAlignment.Right };
btDel.IsEnabled = isdeletable(obj);
if (!btDel.IsEnabled && (img_DeleteDisabled == null))
{
img_DeleteDisabled = new FormatConvertedBitmap(img_Delete, PixelFormats.Gray32Float, null, 0);
}
btDel.Content = (new System.Windows.Controls.Image()
{
Width = 16,
Height = 16,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
Source = btDel.IsEnabled ? img_Delete : img_DeleteDisabled
});
It behaves in the expected way, except.. Well, I'll show you:
The left one is enabled, the right one is disabled
As you can see the alpha channel is gone. How can I integrate it back?
Helped by the comments, I figured out I was thinking about the wrong place where to apply the opacity mask.
The OpacityMask should be applied to the button, not to the image. This is because the opacity applies to the whole image rather than its source.
For this reason, the correct way to implement this is
btDel.Content = (new System.Windows.Controls.Image()
{
Width = 16,
Height = 16,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
Source = btDel.IsEnabled ? img_Delete : img_DeleteDisabled,
OpacityMask = new ImageBrush(img_Delete)
});
This way the mask is applied to the button image. The result is what I needed:
Related
I have a UWP app, which I should start by pointing out that it uses very little XAML. The views are built from JSON object recieved from an API. This means that the vast majority of everything is done in C#, and therefore adds a little complexity to my problem.
I basically want to have a panel (e.g. Grid) that can have rounded corners and have a drop shadow applied to it. The drop shadow should also have the rounded corners, this can be seen in the sample below.
I have looked at the DropShadowPanel as part of the Windows Community Toolkit, but this from what I can tell doesn't do the rounded corners unless I change the content to be a rectangle or some other shape.
To use this as a solution would mean the XAML equivalent of something like:
<Grid>
<toolkit:DropShadowPanel>
<Rectangle />
<toolkit:DropShadowPanel>
<Grid CornerRadius="30">
<!-- My Content -->
</Grid>
</Grid>
To me, this seems like an inefficient use of XAML!
I have also discovered the Composition Pro Toolkit, which to me looks bery interesting as it is all code behind. In particular the ImageFrame control looks to achieve the basis of what I require - although far more advanced than my needs.
The below has been based on the ImageFrame, but doesn't work (content is my grid):
protected FrameworkElement AddDropShadow(FrameworkElement content)
{
var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment, Width = content.Width, Height = content.Height };
var canvas = new Canvas { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
content.Loaded += (s, e) =>
{
var compositor = ElementCompositionPreview.GetElementVisual(canvas).Compositor;
var root = compositor.CreateContainerVisual();
ElementCompositionPreview.SetElementChildVisual(canvas, root);
var shadowLayer = compositor.CreateSpriteVisual();
var frameLayer = compositor.CreateLayerVisual();
var frameContent = compositor.CreateShapeVisual();
root.Children.InsertAtBottom(shadowLayer);
root.Children.InsertAtTop(frameLayer);
frameLayer.Children.InsertAtTop(frameContent);
var rectangle = root.Compositor.CreateRoundedRectangleGeometry();
rectangle.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
rectangle.CornerRadius = new Vector2(30f);
var shape = root.Compositor.CreateSpriteShape(rectangle);
shape.FillBrush = root.Compositor.CreateColorBrush(Colors.Blue);
//var visual = root.Compositor.CreateShapeVisual();
frameContent.Size = rectangle.Size;
frameContent.Shapes.Add(shape);
//create mask layer
var layerEffect = new CompositeEffect
{
Mode = Microsoft.Graphics.Canvas.CanvasComposite.DestinationIn,
Sources = { new CompositionEffectSourceParameter("source"), new CompositionEffectSourceParameter("mask") }
};
var layerEffectFactory = compositor.CreateEffectFactory(layerEffect);
var layerEffectBrush = layerEffectFactory.CreateBrush();
//CompositionDrawingSurface
var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, new Microsoft.Graphics.Canvas.CanvasDevice(forceSoftwareRenderer: false));
var frameLayerMask = graphicsDevice.CreateDrawingSurface(new Size(0, 0), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
layerEffectBrush.SetSourceParameter("mask", compositor.CreateSurfaceBrush(frameLayerMask));
frameLayer.Effect = layerEffectBrush;
var shadow = root.Compositor.CreateDropShadow();
//shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent;
shadow.Mask = layerEffectBrush.GetSourceParameter("mask");
shadow.Color = Colors.Black;
shadow.BlurRadius = 25f;
shadow.Opacity = 0.75f;
shadow.Offset = new Vector3(0, 0, 0);
shadowLayer.Shadow = shadow;
content.Opacity = 0; //hiding my actual content to see the results of this
};
container.Children.Add(canvas);
container.Children.Add(content);
return container;
}
In these tests, I am doing the same inefficient use of object, creating another container that has both the composition canvas, and also the grid. If possible, I'd like to apply the composition directly to the original content grid.
I am completely new to composition, so any thoughts, pointers, glaring errors or solutions would be most welcomed.
A Hack Solution?
I have changed my method to the following, visually it works - but is it right?
protected FrameworkElement AddDropShadow(FrameworkElement content)
{
var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment };
var rectangle = new Rectangle { Fill = new SolidColorBrush(Colors.Transparent) };
content.Loaded += (s, e) =>
{
rectangle.Fill = new SolidColorBrush(Colors.Black);
rectangle.Width = content.ActualWidth;
rectangle.Height = content.ActualHeight;
rectangle.RadiusX = 30;
rectangle.RadiusY = 30;
var compositor = ElementCompositionPreview.GetElementVisual(rectangle).Compositor;
var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
var shadow = compositor.CreateDropShadow();
shadow.BlurRadius = 30f;
shadow.Mask = rectangle.GetAlphaMask();
shadow.Opacity = 0.75f;
visual.Shadow = shadow;
ElementCompositionPreview.SetElementChildVisual(rectangle, visual);
};
container.Children.Add(rectangle);
container.Children.Add(content);
return container;
}
The concept here is that my container grid holds a rectangle and my content grid (or other element).
The first error of this method is that is assumes my input FrameworkElement will be rectangular. I imagine that this could be improved upon by creating a bitmap render of the content as highlighted in this blog - but this will likely be quite costly. I also have to ensure that the rectangle size and shape exactly matches that of my main content!
It feels very wrong that there is a rectangle drawn on the screen (even though hidden by my main content). The rectangle is purely there to create the alpha mask so I guess it could be scrapped if the mask is created from the renderof the content.
I've tried setting the visibility of the rectangle to collapsed to remove it from the visual tree. This means that I can attach the visual to the container instead:
ElementCompositionPreview.SetElementChildVisual(container, visual)
However, doing this means that the shadow displays in front of the main content, which means I need some other ui element to attach it too - may as well be the rectangle!
Your solution to use Rectangle is my current workaround everywhere I need rounded shadow under Grid or Border. It's simple and it's plain, why should I complain :)
But if it's not your choice you can draw a rounded rectangle and blur it:
GraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(Compositor, CanvasDevice.GetSharedDevice());
var roudRectMaskSurface = GraphicsDevice.CreateDrawingSurface(new Size(SurfaceWidth + BlurMargin * 2, SurfaceHeight + BlurMargin * 2), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(roudRectMaskSurface))
{
ds.Clear(Colors.Transparent);
ds.FillRoundedRectangle(new Rect(BlurMargin, BlurMargin, roudRectMaskSurface.Size.Width + BlurMargin, roudRectMaskSurface.Size.Height + BlurMargin), YourRadius, YourRadius, Colors.Black);
}
var rectangleMask = Compositor.CreateSurfaceBrush(roudRectMaskSurface);
Now you can apply this surface in the EffectBrush with blur effect to obtain custom shadow.
BlurMargin - corresponds to the blur amount, you need it because your blurred surface will be bigger than initial source rectangle (to avoid blur clip).
I want to add emoticons to WPF chat application. I know that wpf don't supports emoticons for that reason I am replacing emoticons with image. I am using inline property of textBlock to add images to textBlock but, I having problem with alignment of images. I am not able to make emoticon images to get properly aligned. I am sharing a screenshot of how it is looking.
Screenshot of app window
This is how emoticon is looking
the example is just a demo where I am adding elements in constructor only to see how it will look. I am sharing my code as well.
#out.Inlines.Add(new Run("Hii, my name is Ajay!!"));
Image emo = new Image();
emo.Height = 15;
emo.Width = 15;
emo.VerticalAlignment = VerticalAlignment.Bottom;
emo.Margin = new Thickness(3, 0, 0, 0);
emo.Source = new BitmapImage(new Uri(#"C:\Users\admin\Desktop\test1.jpg", UriKind.RelativeOrAbsolute));
// InlineUIContainer container = new InlineUIContainer(emo);
#out.Inlines.Add(emo);
Is there any way I can make emoticon image properly align? Is it OK to use textblock or I should use any other control for this?
Any help is highly appreciated.
A few potential options may be:
set the Top margin on the Image. it's in the format of LEFT, TOP, RIGHT, BOTTOM
emo.Margin = new Thickness(3, 4, 0, 0);
Another option is to wrap the image in a Run and set the BaselineAlignment. https://msdn.microsoft.com/en-us/library/system.windows.baselinealignment
var imageRun= new Run(emo);
imageRun.BaselineAlignment = BaselineAlignment.TextBottom; //experiment with the other enum options
#out.Inlines.Add(imageRun);
adjust the text rather than the image (though i would keep trying with the image and use this as a last resort).
var textRun = new Run("Hii, my name is Ajay!!");
textRun.Margin = experiment;
textRun.BaselineAlignment = experiment;
#out.Inlines.Add(textRun );
I tried as #Bill Tarbell suggested and it worked for me.
Final working code is as follows:
var textRun = new Run("Hii, my name is Ajay!!");
textRun.BaselineAlignment = BaselineAlignment.Center;
#out.Inlines.Add(textRun);
Image emo = new Image();
emo.Height = 20;
emo.Width = 20;
emo.VerticalAlignment = VerticalAlignment.Bottom;
emo.Margin = new Thickness(3, 0, 0, 0);
emo.Source = new BitmapImage(new Uri(#"C:\Users\admin\Desktop\test1.jpg", UriKind.RelativeOrAbsolute));
// InlineUIContainer container = new InlineUIContainer(emo);
#out.Inlines.Add(emo)
I'm following an example in the Microsoft Band SDK documentation to fill a button with a theme color but the code doesn't compile as Color and ThemeColor.BandBase do not exist in any packages. The code in the example is:
FilledPanel panel = new FilledPanel
{
Rect = new PageRect(0, 0, 245, 102),
Color = ThemeColor.BandBase
};
I'm assuming Color should be BackgroundColor as that property exists, but I can't find anything similar to ThemeColor that I can use. Does anyone know what I can use?
UPDATE: I think I may have solved it with this:
BandTheme theme = await bandClient.PersonalizationManager.GetThemeAsync();
// create a filled rectangle to provide the background for a button
FilledPanel panel = new FilledPanel
{
Rect = new PageRect(0, 0, 245, 102,
BackgroundColor = theme.Base
};
Yet to test. If it works will add as answer
I believe the method you've chosen should work.
You can also use the static accessor:
FilledPanel panel = new FilledPanel
{
Rect = new PageRect(0, 0, 245, 102,
BackgroundColor = ThemeColor.BandBase
};
It is possible to set SizeMode Zoom and apply padding?
The following will work:
ibPic2DLeft.SizeMode = PictureBoxSizeMode.Normal;
ibPic2DLeft.Padding = new Padding(100, 100, 50, 50);
The following will not work:
ibPic2DLeft.SizeMode = PictureBoxSizeMode.Zoom;
ibPic2DLeft.Padding = new Padding(100, 100, 50, 50);
What are the alternatives of padding that are compatible with zoom?
Put the PictureBox inside a Panel.
Set the PictureBox to Dock=Fill.
Set PictureBox SizeMode=Zoom.
Apply Padding to the Panel.
you can better go for this approach, when your image size is less than the size of the picture box, you can use normal mode and when your picture size is bigger than picture box you can use zoom mode. This is the best dynamic approach i found for my application solution in past.
Image oImg = yourImage;
if ((oImg.Height > ibPic2DLeft.Height | oImg.Width > ibPic2DLeft.Width)) {
ibPic2DLeft.SizeMode = PictureBoxSizeMode.Normal;
} else {
ibPic2DLeft.SizeMode = PictureBoxSizeMode.Zoom;
}
I have a little problem. I am enabling the user to choose the size of a textblock in which he is able to display text by doing some other stuff.
My problem here is that I have to add a border to the textblock to show the user how big it became.
When I applied the following code, my program just crashes in that scenario:
//create a TextBlock at desired position
TextBlock tmpTextBlock = new TextBlock
{
Width = 166,
Height = Math.Max(tmpY1, tmpY2) - Math.Min(tmpY1, tmpY2),
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(0, Math.Min(tmpY1, tmpY2) - 50, 0, (int)WeekGrid.ActualHeight - Math.Max(tmpY1, tmpY2)),
Text = "Type stuff here"
};
tmpTextBlock.Holding += tmpTextBox_Holding;
tmpTextBlock.RightTapped += tmpTextBox_RightTapped;
WeekGrid.Children.Add(tmpTextBlock);
Grid.SetRow(tmpTextBlock, 1);
//add the border - these lines produce the problem
Border border = new Border
{
Child = tmpTextBlock,
Background = this.Resources["ApplicationPageBackgroundThemeBrush"] as SolidColorBrush,
BorderBrush = this.Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush,
BorderThickness = new Thickness(1),
};
The Exception that follows is an argument exception:
Value does not fall within the expected range.
EDIT
Whoops I've solved that problem. I had to remove adding the Textblock into the grid.
The problem I have now is that the border appears around the grid - not around the textblock!
The following code made that possible:
Border border = new Border
{
Child = tmpTextBlock,
Background = this.Resources["ApplicationPageBackgroundThemeBrush"] as SolidColorBrush,
BorderBrush = this.Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(24)
};
WeekGrid.Children.Add(border);
Grid.SetRow(border, 1);
EDIT 2
Problem solved again.
I of course had to remove the margin setting of the textblock!
Thank you very much!
Greetings,
FunkyPeanut
Could you post the exception?
clearly there is an error in your code here:
WeekGrid.Children.Add(tmpTextBlock);
Grid.SetRow(tmpTextBlock, 1);
//add the border - these lines produce the problem
Border border = new Border
{
Child = tmpTextBlock,
Background = this.Resources["ApplicationPageBackgroundThemeBrush"] as SolidColorBrush,
BorderBrush = this.Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush,
BorderThickness = new Thickness(1),
};
The component Border it is like a "container", which accepts a single element, you should switch to:
Border border = new Border
{
Child = tmpTextBlock,
Background = this.Resources["ApplicationPageBackgroundThemeBrush"] as SolidColorBrush,
BorderBrush = this.Resources["ApplicationForegroundThemeBrush"] as SolidColorBrush,
BorderThickness = new Thickness(1),
};
WeekGrid.Children.Add(border);
Grid.SetRow(border, 1);
You should also check if the resources are "available" for your access.