Update text in adorner on button click - wpf

I have created my custom adorner to cover my main window with a gray canvas alongwith a textblock at center to show some status text while i was working on other window.
What i am currently doing is fetching the required adornerElement(ie Canvas with a textblock) from my resources and passing it to an adorner in my view constructor like this -
ResourceDictionary reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
UIElement adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as UIElement;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
But i want to update that text in textblock in some scenarios say if i click on some button in other window but how to update the text dynamically??
Adorner element from Resource file-
<Grid x:Key="RefreshingReportAdorner">
<Rectangle Fill="Gray"
StrokeThickness="1"
Stroke="Gray"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<Border BorderBrush="Black"
BorderThickness="2"
Background="White"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock i18n:LanguageManager.VisualId="6"
Text="Some Text(Update dynamically)"
Padding="15,10,15,10"/>
</Border>
</Grid>
Let me know if additional code or approach required..

Have you tried to create some model and push it to RefreshingReportAdorner element's DataContext?
Code:
var reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
var adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as FrameworkElement;
var model = new Model();
model.MyText = "Initial text";
adornerElement.DataContext = model;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
...
model.MyText = "Text after click";
XAML:
<TextBlock i18n:LanguageManager.VisualId="6"
Text="{Binding MyText}"
Padding="15,10,15,10"/>
Model:
public class Item : INotifyPropertyChanged
{
private string _myText;
public string MyText
{
get
{
return this._myText;
}
set
{
this._myText= value;
this.OnPropertyChanged("MyText");
}
}
}

Related

WPF ScrollViewer not working in Grid programmatically populated

I have this situation in xaml
<StackPanel Orientation="Vertical" Margin="5" Background="AliceBlue" Height="370">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Name="DescriptionsGrid" MinHeight="0" MaxHeight="370" ScrollViewer.CanContentScroll="True"></Grid>
</ScrollViewer>
</StackPanel>
and I'm filling the Grid with the following
DescriptionsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
TextBlock textBlock = new TextBlock { Text = description, TextWrapping = TextWrapping.WrapWithOverflow };
Label label = new Label { Content = textBlock, Foreground = new SolidColorBrush(Colors.Blue) };
label.MouseLeftButtonDown += new MouseButtonEventHandler(LoadXML);
DescriptionsGrid.Children.Add(label);
Grid.SetRow(DescriptionsGrid.Children[DescriptionsGrid.Children.Count - 1], description_counter);
description_counter++;
I can't define rows height as the description may be long enought to wrap the text to new line.
The scrollbar does not appears and new elements go hidden down below.
Any idea?
Following the suggestion of #Clemens I rewrote the code, and found that the mistake was in the XML.
To populate the grid
foreach (HostMessages hostMessage in hostMessagesList)
{
Label message = new Label
{
Content = new TextBlock {
Text = hostMessage.Description,
TextWrapping = TextWrapping.WrapWithOverflow
},
Foreground = new SolidColorBrush(Colors.Blue),
Cursor = Cursors.Hand
};
message.MouseLeftButtonDown += new MouseButtonEventHandler(LoadXML);
itemsControl.Items.Add(message);
}
The XML is as follows
<DockPanel Grid.Row="1" Margin="5" Background="AliceBlue" Height="370">
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="370">
<ItemsControl Name="itemsControl"></ItemsControl>
</ScrollViewer>
</DockPanel>
The mistake was assigning a MaxHeight="370" to the Grid and none to the ScrollViewer.

how to select an image in a list of image in wrappannel wpf

I add images from the filedialog in my programm. i want to kow how i can give them attribute like select_event on click for example to remove one.
Thanks in advance
XAML Code .
<Grid DockPanel.Dock="Top" Margin="5,5,5,5" Background="#FFA59A9A">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<WrapPanel Name="Picture_Holder" HorizontalAlignment="Center" Orientation="Horizontal"/>
</ScrollViewer>
</Grid>
C# code
OpenFileDialog OpenFile = new OpenFileDialog();
OpenFile.Multiselect = true;
OpenFile.Title = "Select Picture(s)";
OpenFile.Filter = "ALL supported Graphics| *.jpeg; *.jpg;*.png;";
if (OpenFile.ShowDialog() == true)
{
foreach(String file in OpenFile.FileNames)
{
Add_Image(file);
}
}
private void Add_Image(string file)
{
Console.WriteLine("Une image"+file);
Image new_img = new Image();
new_img.Source = new BitmapImage(new Uri(file));
Thickness img_thickness = new Thickness();
img_thickness.Bottom = 2;
img_thickness.Left = 2;
img_thickness.Right = 2;
img_thickness.Top = 2;
new_img.Margin = img_thickness;
new_img.MaxWidth = new_img.MaxHeight = 105;
Picture_Holder.Children.Add(new_img);
}
You should use a ListBox like shown below, because it has built-in support for item selection. The WrapPanel would go into the ItemsPanel property of the ListBox.
<ListBox x:Name="imageListBox" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" MaxWidth="105" MaxHeight="105" Margin="2"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then assign a collection of file path strings to its ItemsSource property:
imageListBox.ItemsSource = OpenFile.FileNames;
You can now get the file path of the selected image by the SelectedItem property of the ListBox.
In order to use BitmapImage items instead of strings - and thus get a BitmapImage as SelectedItem - write:
imageListBox.ItemsSource = OpenFile.FileNames
.Select(path => new BitmapImage(new Uri(path)));
The next step would be to have a view model with a property that holds the image collection:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
private ImageSource selectedImage;
public ImageSource SelectedImage
{
get { return selectedImage; }
set
{
selectedImage = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedImage)));
}
}
}
You would assign an instance of the view model to the DataContext property of the MainWindow
DataContext = new ViewModel();
and bind to its properties in XAML:
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Images}"
SelectedItem="{Binding SelectedImage}">
...
</ListBox>
To populate the Images collection in the view model:
var vm = (ViewModel)DataContext;
vm.Images.Clear();
foreach (var path in OpenFile.FileNames)
{
vm.Images.Add(new BitmapImage(new Uri(path)));
}
Remove the selected one by e.g.
vm.Images.Remove(vm.SelectedImage);
which could be executed in an ICommand in the view model.

WPF, interesting things when binding with TemplatedParent

Control template:
<ControlTemplate x:Key="BasicShape2">
<StackPanel Name="sp">
<Border Name="bd" CornerRadius="3.5" BorderThickness="1" BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent},Path=DataContext.NodeType, Converter={StaticResource NodeTypeColorConverter}, Mode=OneWay}" Height="32" Padding="1">
<TextBlock Name="tbName" Grid.Column="1" Text="" HorizontalAlignment="Center" VerticalAlignment="Bottom" FontSize="16" />
</Border>
</StackPanel>
</ControlTemplate>
a class which this template will apply to:
public class MyThumbEx : Thumb
{
public static readonly DependencyProperty MemberInfoProperty = DependencyProperty.Register("MemberInfo", typeof(FamilyMemberInfo), typeof(MyThumbEx));
public FamilyMemberInfo MemberInfo
{
get { return (FamilyMemberInfo)GetValue(MemberInfoProperty); }
set { SetValue(MemberInfoProperty, value); }
}
public MyThumbEx(ControlTemplate template, FamilyMemberInfo info, Point position)
{
this.MemberInfo = info;
this.DataContext = this.MemberInfo;
this.Template = template;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.ApplyTextContent();
}
public void ApplyTextContent()
{
TextBlock tbName = this.Template.FindName("tbName", this) as TextBlock;
if (tbName != null)
{
tbName.Text = this.MemberInfo.Name;
}
}
}
initialize and display it on a canvas:
public MainWindow()
{
InitializeComponent();
//
FamilyMemberInfo mi = new FamilyMemberInfo();
mi.Name = "someone";
mi.ID = "id1";
MyThumbEx te = new MyThumbEx(Application.Current.Resources["BasicShape2"] as ControlTemplate, mi, new Point(0, 0));
//
this.cvMain.Children.Add(te);
}
These codes work fine, but be noticed that in the control template, I have to set Path=DataContext.NodeType, not just Path=NodeType. I'm new to WPF, and I found that normally, when I did binding without using this template stuff, I didn't need to specify the predicate 'DataContext', right? Why we need here?
Another thing I found is, I can comment out this.DataContext = this.MemberInfo, and change binding path to Path=MemberInfo.NodeType, the code still works fine. Could anyone explain that for me?
Thanks in advance!
If you dont change the DataContext manuelly, every child automatically has the DataContext of its Parent. So if your Window has f.e. the ViewModel as DataContext all of its Controls have access to the ViewModels Properties through {Binding Path=Property}.
But in case of a ControlTemplate the usual typical flow where DataContext just cascades through from the parent to child doesn’t apply here. So you have to set the DataContext first, either through Property="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.Property}" or DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}" Property="{Binding Path=Property}".
To your second point: It could be, that the ControlTemplate automatically uses the code-behind of its containing Element as DataContext, so you can use the code-behinds properties without setting the DataContext, but I am not 100% sure about this.

Access the TextBlock 's text at run time in DataTemplate of ItemsControl in WPF

Iam displaying messages in my WPF application
when a new message is added to the messages, i need to highlight it.so i want to dynamically get the text added to TextBlock
i have the xaml like this
<ItemsControl Name="DialogItemsControl" ItemsSource="{Binding Messages, Mode=OneWay}" Background="Transparent"
BorderBrush="Transparent" TargetUpdated="DialogItemsControl_TargetUpdated">
<ItemsControl.ItemTemplate><!-- For ever message -->
<DataTemplate>
<Grid Margin="0,0,0,20">
<ItemsControl Name="SubDialogItemsControl"
Foreground="{DynamicResource ButtonTextBrush}"
ItemsSource="{Binding Lines,NotifyOnTargetUpdated=True}"
Margin="0,0,0,12"
Grid.Column="0">
<ItemsControl.ItemTemplate><!-- For every line -->
<DataTemplate>
<TextBlock Name="DialogMessageText"
Text="{Binding NotifyOnTargetUpdated=True}"
VerticalAlignment="Top"
Margin="0,2,0,2"
TextTrimming="WordEllipsis"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and the code in the codebehind class is like this:
private void DialogItemsControl_TargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
ItemsControl itemControl = sender as ItemsControl;
ContentPresenter dp = itemControl.ItemContainerGenerator.ContainerFromItem(itemControl.Items.CurrentItem) as ContentPresenter;
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = dp.ContentTemplate;
ItemsControl itc = (ItemsControl)myDataTemplate.FindName("SubDialogItemsControl", dp);
if (itc != null && itc.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
ContentPresenter cp = itc.ItemContainerGenerator.ContainerFromIndex(0) as ContentPresenter;
DataTemplate dt = cp.ContentTemplate;
TextBlock tb = dt.LoadContent() as TextBlock;
tb.TargetUpdated += new EventHandler<System.Windows.Data.DataTransferEventArgs>(myTextBlock_TargetUpdated);
}
}
void myTextBlock_TargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
TextBlock tb = sender as TextBlock;
//When i access the text property of tb, its showing null, how to get the text
}
When i access the text property of textblock in the target updated event of textblock, its showing null, how to read the text.
Thanks in advance
You tackle the problem from the wrong angle (and probably add a memory leak in the process since I don't see you unsubscribing to the event).
You need to create a Custom TextBlock, overriding the metadata of the Text property so that it changes the Background for a few seconds when the text string changes (through PropertyChangedCallback).
And then use that custom TextBlock in the DataTemplate of your ItemsControl.
EDIT - I thought other people could need this feature so here is a working example:
public class CustomTextBlock : TextBlock
{
static CustomTextBlock()
{
TextProperty.OverrideMetadata(typeof(CustomTextBlock), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(
(dpo, dpce) =>
{
//Flash the background to yellow for 2 seconds
var myTxtblk = dpo as CustomTextBlock;
if (myTxtblk != null)
{
myTxtblk.Background = Brushes.Yellow;
Task.Factory.StartNew(
() =>
{
Thread.Sleep(2000);
Application.Current.Dispatcher.Invoke(
new Action(() =>
{
myTxtblk.Background = Brushes.Transparent;
}));
});
}
})));
}
}
Then you need to declare the right xmlns namespace in your XAML view, and you use it like a regular TextBlock:
<local:CustomTextBlock Text="{Binding MyDynamicText}"/>
It will flash yellow when MyDynamicText is modified (provided it raises PropertyChanged).

Showing a ContextMenu

I'm having difficulty finding good documentation for this, despite searching for a while.
I'd like to have a context menu in my app that replicates the behavior seen with other tap-and-hold context menus, like pinning an app to the start screen from the app list.
Here is my Context menu:
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu x:Name="sectionContextMenu">
<toolkit:MenuItem Header="Hide this section from this list" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
How do I make it show?
The context menu needs to be attached to the element that you want the user to tap and hold.
<Border Margin="0,12" BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="2" Background="Transparent" VerticalAlignment="Center" Padding="16">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu x:Name="sectionContextMenu">
<toolkit:MenuItem Header="Hide this section from this list" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="Tap and hold here to invoke a ContextMenu" Style="{StaticResource PhoneTextNormalStyle}"/>
</Border>
The user can now invoke the context menu with a tap and hold on the content of this Border element.
Unique context menu for different items depending on the content.
private ContextMenu CreateContextMenu(ListBoxItem lbi)
{
ContextMenu contextMenu = new ContextMenu();
ContextMenuService.SetContextMenu(lbi, contextMenu);
contextMenu.Padding = new Thickness(0);
string item_1 = "item 1";
if(lbi.Content is string) {
item_1 = lbi.Content as string;
}
contextMenu.ItemsSource = new List<string> { item_1, "item 2", "item 3" };
contextMenu.IsOpen = true;
return contextMenu;
}
private void Results_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (Results.SelectedIndex == -1) return;
int index = Results.SelectedIndex;
ListBoxItem lbi = Results.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
CreateContextMenu(lbi);
Results.SelectedIndex = -1;
}

Resources