I am populating the list view items dynamically. At the same time I wanna display a progress bar. When data populated the progress bar should be disabled. How to achieve this.
I am new in WPF.
You can use the BackgroundWorker class, which simplifies the handling of background threads when you are working with WPF.
There are plenty of examples on the web for this. Here two from codeproject, but it's easy to find more examples:
http://www.codeproject.com/Tips/83317/BackgroundWorker-and-ProgressBar-demo.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
I prefer to control view state via presentation model. When view needs to populate items in address to presentation model that starts Worker thread and updates its progress values on UI synchronization context.
public class SampleModel : ObservableObject
{
private ObservableCollection<string> _items = new ObservableCollection<string>();
public IEnumerable<string> Items
{
get
{
return this._items;
}
}
private int _progress;
public int Progress
{
get
{
return this._progress;
}
set
{
if (this._progress != value)
{
this._progress = value;
this.OnPropertyChanged("Progress");
}
}
}
public void Fill()
{
this.Progress = 0;
var sc = SynchronizationContext.Current;
new Thread(new ThreadStart(() =>
{
for (int i = 0; i < 100; i++)
{
sc.Post(p =>
{
this._items.Add(i.ToString());
this.Progress ++;
}, null);
Thread.Sleep(100);
}
sc.Post(p =>
{
this.Progress = 0;
}, null);
}))
.Start();
}
}
XAML:
<DockPanel>
<ProgressBar Minimum="0"
Maximum="100"
Height="50"
Value="{Binding Progress}"
DockPanel.Dock="Top">
<ProgressBar.Style>
<Style TargetType="ProgressBar">
<Setter Property="IsEnabled"
Value="True"/>
</Style>
</ProgressBar.Style>
</ProgressBar>
<Button Name="Start"
DockPanel.Dock="Top">Start</Button>
<ListView Name="List"
ItemsSource="{Binding Items}"/>
</DockPanel>
And code behind:
public MainWindow()
{
InitializeComponent();
this.Model = new SampleModel();
this.Start.Click += new RoutedEventHandler(Start_Click);
}
void Start_Click(object sender, RoutedEventArgs e)
{
this.Model.Fill();
}
protected SampleModel Model
{
get
{
return (SampleModel)this.DataContext;
}
set
{
this.DataContext = value;
}
}
Related
I'm don't know how to get focused item automatically change within a ListView.
I would like the focused item in the view to automatically change when I change the "IsSelected" property to an other element in the databinded list:
When an item is modified by PC/SC card reader (see this as input), the next element should be focused like this:
I would like to stay in MVVM and therefor not having View referenced in the ViewModel. Below is my current code.
Model : The main purpose is to extend a DTO with an IsSelected property and implementing INotifyPropertyChanged
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsSelected;
private DtoReader _dtoReader;
public SmartDeviceModel(DtoReader _reader)
{
_dtoReader = _reader;
}
public string DisplayName => _dtoReader.DisplayName;
public string Uid
{
get
{
return _dtoReader.Uid;
}
set
{
_dtoReader.Uid = value;
OnPropertyChanged("Uid");
}
}
public long RadioId
{
get
{
return _dtoReader.RadioId : _dtoMarker.RadioId;
}
set
{
_dtoReader.RadioId = value;
OnPropertyChanged("RadioId");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
ViewModel received events of a PC/SC card reader to pair data from RFID chip with current selected item. When RFID chip is removed from PC/SC Reader, the next element is well selected but got not focused.
public class ScanDeviceViewModel : BaseViewModel
{
public BindingList<SmartDeviceModel> ReaderList { get; }
public int SelectedReaderIndex;
private ITagReaderInput _rfidReader;
public ScanDeviceViewModel()
{
//Get Data listener for RFID Tag
_rfidReader = new IdentivTagReader.IdentivTagReader();
// Data Source of DTO
SiteInteractor siteInterractor = new SiteInteractor();
// List used for DataBinding
ReaderList = new BindingList<SmartDeviceModel>();
foreach (DtoReader m in SiteInteractor.GetReaders().OrderBy(x => x.DisplayName))
{
ReaderList.Add(new SmartDeviceModel(m));
}
if (ReaderList.Count() > 0)
{
for (var i = 0; i < ReaderList.Count(); i++)
{
if (String.IsNullOrEmpty(ReaderList[i].Uid))
{
SelectedReaderIndex = i;
ReaderList[i].IsSelected = true;
break;
}
}
}
_rfidReader.LabelDetected += RfidTagDetected;
_rfidReader.LabelRemoved += RfidRemoved;
}
private void RfidTagDetected(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex] != null && string.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid))
{
ReaderList[SelectedReaderIndex].IsSelected = true;
ReaderList[SelectedReaderIndex].Uid = tag.Uid;
ReaderList[SelectedReaderIndex].RadioId = tag.RadioId;
}
}
private void RfidRemoved(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex].Uid == tag.Uid)
{
ReaderList[SelectedReaderIndex].IsSelected = false;
while (ReaderList.Count >= SelectedReaderIndex + 1)
{
SelectedReaderIndex++;
if (String.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid)){
ReaderList[SelectedReaderIndex].IsSelected = true;
break;
}
}
}
}
}
View I'm using a "Setter" using databinding to my model property "IsSelected" as suggested here but I most missed something else I don't understand yet.
<ListView ItemsSource="{Binding ReaderList}"
Margin="5" x:Name="listViewReader" SelectionMode="Single"
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox Grid.Row ="0" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Bottom" MaxHeight="90">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="{Binding DisplayName}" />
<DockPanel Grid.Row="1">
<Label Content="UID"/>
<Label Content="{Binding Uid}"/>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="1">
<Label Content="RadioID" />
<Label Content="{Binding RadioId}"/>
</DockPanel>
</Grid>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I tried several approach like this answer, although item is well selected, it is not focused.
I finally figure it out. Below is my current working code.
In the Model I have just changed the flag IsSelected to IsCurrent to avoid confusion with ListViewItem built-in property but it might just be an implementation detail.
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsCurrent;
[...]
}
The BindingList in ViewModel is mostly the same as in OP:
public class ScanDeviceViewModel : INotifyPropertyChanged
{
public BindingList<SmartDeviceModel> ReaderList { get; internal set; }
[...]
}
NB : BindingList seems to reduce OnNotifyPropertyChange need but other Type of List should work with a tiny bit of extra code. I also noticed BindingList might not be suited for huge list scenario.
The View is then using the above ViewModel as DataContext and therefore Binding ItemSource to the BindingList. The ListViewItem Style Setter is then using the IsCurrent Property from the Model.
<ListView ItemsSource="{Binding ReaderList}"
SelectionMode="Single"
SelectionChanged="OnListViewSelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsCurrent}" />
</Style>
</ListView.ItemContainerStyle>
[...]
And finally this piece of View Code behind below is mainly to simulate the focus as per user input, otherwise the elemant get selected but not focused and might be outside the visible item scope :
private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView listView = e.Source as ListView;
if (listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) is FrameworkElement container)
{
container.Focus();
}
}
According to MVVM you can implement custom Interaction Behavior:
Import to XAML: xmlns:b="http://schemas.microsoft.com/xaml/behaviors" (if you are using .NET Core 3.1 - 5)
Add to content-body:
<ListView ...>
<b:Interaction.Behaviors>
<local:AutoScrollToLastItemBehavior />
</b:Interaction.Behaviors>
</ListView>
Finally add the next class:
public sealed class AutoScrollToLastItemBehavior : Microsoft.Xaml.Behaviors.Behavior<ListView>
{
// Need to track whether we've attached to the collection changed event
bool _collectionChangedSubscribed = false;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += SelectionChanged;
// The ItemSource of the listView will not be set yet,
// so get a method that we can hook up to later
AssociatedObject.DataContextChanged += DataContextChanged;
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView();
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ScrollIntoView();
}
private void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The ObservableCollection implements the INotifyCollectionChanged interface
// However, if this is bound to something that doesn't then just don't hook the event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && !_collectionChangedSubscribed)
{
// The data context has been changed, so now hook
// into the collection changed event
collection.CollectionChanged += CollectionChanged;
_collectionChangedSubscribed = true;
}
}
private void ScrollIntoView()
{
int count = AssociatedObject.Items.Count;
if (count > 0)
{
var last = AssociatedObject.Items[count - 1];
AssociatedObject.ScrollIntoView(last);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= SelectionChanged;
AssociatedObject.DataContextChanged -= DataContextChanged;
// Detach from the collection changed event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && _collectionChangedSubscribed)
{
collection.CollectionChanged -= CollectionChanged;
_collectionChangedSubscribed = false;
}
}
}
I am building a panorama which displays images through binding. I need to find index of panorama item whenever the current item changes. But the SELECTIONCHANGED event is not firing in case when data is retrieved through binding. Can you please suggest some other way. Thanx in advance
XAML Code
<phone:Panorama x:Name="HeaderPanorama"
ItemsSource="{Binding PanoramaImages}"
Width="550" Margin="-10,-255,0,-140"
SelectionChanged="HeaderPanorama_SelectionChanged_1">
<phone:Panorama.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Margin="-10"/>
</DataTemplate>
</phone:Panorama.ItemTemplate>
</phone:Panorama>
CodeBehind
private void HeaderPanorama_SelectionChanged_1(
object sender,
SelectionChangedEventArgs e)
{
if (this.DataContext != null && this.DataContext is HomeViewModel)
{
((HomeViewModel)this.DataContext).PanoramaItemIndex =
HeaderPanorama.SelectedIndex;
}
}
ViewModel Code
public HomeViewModel()
{
RequestHomeData();
PanoramaImages = new List<string>();
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
PanoramaImages.Add("/Assets/n.png");
}
private List<string> _panoramaImages;
public List<string> PanoramaImages
{
get { return _panoramaImages; }
set
{
_panoramaImages = value;
NotifyPropertyChanged("PanoramaImages");
}
}
private int _panoramaItemIndex;
public int PanoramaItemIndex
{
get { return _panoramaItemIndex; }
set
{
_panoramaItemIndex = value;
NotifyPropertyChanged("PanoramaItemIndex");
}
}
I am trying to dynamically populate a WPF tree by using a ViewModel, however, for some reason it's not working. Either the bindings either aren't properly or I am messing up somewhere in code behind.
Here's a sample of what I have.
In XAML I define my TreeView like so...
<TreeView DockPanel.Dock="Left" Width="200" DataContext="{Binding MessageTree}" ItemsSource="{Binding MessageTree}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Subject}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In Code Behing i have...
private Mail MessageTree { get; set; }
And
using (var mail = new MailParser())
{
int count = mail.GetMessageCount(DateTime.Today.AddDays(-10), DateTime.Today.AddDays(1));
MessageTree = new Mail();
for (int i = count - 1; i >= 0; i--)
{
MailMessage msg = mail.RetrieveMessage(i);
if (msg != null)
{
MessageTree.Add(msg);
}
if (backgroundWorker != null)
{
decimal perc = (100.0m - (((i + 1.0m)*100.0m)/count));
backgroundWorker.ReportProgress((int) perc, "Recebendo mensagens... " + perc.ToString("N2") + "%");
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
}
}
}
Mail is defined as
public sealed class Mail : INotifyPropertyChanged
{
private readonly ObservableCollection<Mail> _children;
private readonly MailMessage _msg;
private readonly Mail _parent;
private bool _isExpanded;
private bool _isSelected;
public Mail()
{
_msg = new MailMessage {Subject = "Empty"};
_parent = null;
_children = new ObservableCollection<Mail>();
}
public Mail(MailMessage msg, Mail parent = null)
{
_msg = msg;
_parent = parent;
_children = new ObservableCollection<Mail>();
}
public IEnumerable<Mail> Children
{
get { return _children; }
}
public string Subject
{
get { return _msg.Subject; }
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
OnPropertyChanged();
}
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Add(MailMessage msg)
{
_children.Add(new Mail(msg, this));
OnPropertyChanged("Children");
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I can't find anything in it so different from examples found online that it wouldn't work. The Add method is incomplete, I still need some logic to decide whether to add them to the collection or to the collection of one of the collection members, but as is all my Mail objecys are beeing added to the collection but not showing up in the TreeView.
What totally obvious thing am i missing? Shouldn't the TreeView automaticly update as I add items to the collection?
What I want is for the TreeView to show The children of the MessageTree property, and those children's children.
EDIT: Couldn't see the whole thing on my phone - amended answer based on ability to actually see everything. :)
MOREEDIT: updated based on comments, let's start from scratch!
First off, if you're set on using the window/whatever as the datacontext, let's make it `INotifyPropertyChange...next, let's make "MessageTree" a collection of mails, not just a single one (it'll make binding semantics easier, trust me)
public class WhateverContainsTheTree : Window, INotifyPropertyChanged
{
public WhateverContainsTheTree()
{
this.Loaded += OnLoaded;
this._messageTree = new ObservableCollection<Mail>();
this.DataContext = this;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker();
_worker.DoWork += WorkerWorkin;
_worker.RunWorkerAsync();
}
private BackgroundWorker _worker;
private ObservableCollection<Mail> _messageTree;
public ObservableCollection<Mail> MessageTree
{
get { return _messageTree; }
set { _messageTree = value; RaisePropertyChanged("MessageTree"); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void WorkerWorkin(object sender, DoWorkEventArgs e)
{
// obviously, change this to your stuff; I added a ctor so I could pass a string
Thread.Sleep(3000);
Console.WriteLine("Ok, setting message tree");
Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
(Action)(() =>
{
var mail1 = new Mail("Mail#1:Changed from background thread");
var mail2 = new Mail("Mail#2:Submail of mail #1");
var mail3 = new Mail("Mail#3:Submail of mail #2");
var mail4 = new Mail("Mail#4:Submail of mail #1");
var mail5 = new Mail("Mail#5:Submail of mail #4");
mail1.Children.Add(mail2);
mail1.Children.Add(mail4);
mail2.Children.Add(mail3);
mail4.Children.Add(mail5);
MessageTree.Add(mail1);
})
);
}
}
Also, like I'd said in the original response, let's slightly tweak Mail.Children:
public ObservableCollection<Mail> Children
{
get { return _children; }
}
And here's what I used for the treeview xaml:
<TreeView DockPanel.Dock="Left" Width="200" ItemsSource="{{Binding MessageTree}}">
<TreeView.ItemContainerStyle>
<Style TargetType="{{x:Type TreeViewItem}}">
<Setter Property="IsExpanded" Value="{{Binding IsExpanded, Mode=TwoWay}}" />
<Setter Property="IsSelected" Value="{{Binding IsSelected, Mode=TwoWay}}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{{Binding Children}}">
<TextBlock Text="{{Binding Subject}}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
If this STILL doesn't work, I'll just paste in the whole LINQPad blob I put together to test this.
Without seeing the entire setup, I'm not positive but my guess would be that since MessageTree is a plain CLR property (rather than something that raises PropertyChanged or a DependencyProperty or something, that the binding is occurring before your MessageTree = new Mail(); call. When you set it to a new instance, the binding system isn't getting notified since it is a plain property.
Another potential issue is that you say that code is in the code-behind. Just using that Binding syntax won't pick up a property from the code-behind. It's possible that you're setting that up somewhere else in the code that you didn't show us. But generally you aren't going to be binding from the View to the code-behind, you'd be binding to a ViewModel that was used as the DataContext for the view itself.
Had to give a name to the TreeView (Tree) and then after
MessageTree = new Mail();
insert
Tree.ItemSource = MessageTree.Children;
I find this ugly but at least it works now. Thank you all for trying to help.
Does anyone knows any good tool for drawing correlation heat maps for WPF?
Example based on comments:
Original image source
The free WPF Toolkit has a TreeMap. You can define it in XAML as follows:
<vis:TreeMap ItemsSource="{Binding Path=HeatMap.Sectors}"
Interpolators="{StaticResource colourInterpolator}">
<vis:TreeMap.ItemDefinition>
<vis:TreeMapItemDefinition ValueBinding="{Binding MarketCap}">
<DataTemplate>
<Grid>
<Border x:Name="Border"
BorderBrush="Black"
BorderThickness="1"
Margin="1"
Opacity="0.5">
</Border>
<TextBlock Text="{Binding Name}"
TextWrapping="Wrap"
FontSize="20"
Margin="5"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</vis:TreeMapItemDefinition>
</vis:TreeMap.ItemDefinition>
</vis:TreeMap>
The above XAML is a snippet from an application I have written that shows financial HeatMaps. You can see a Silverlight version running here:
http://www.scottlogic.co.uk/blog/colin/xaml-finance/
(Just hit the 'heatmap' button)
If you are looking for a commercial product, I would suggest you look at the Telerik controls. Telerik has excellent controls for WPF. Included in the long list is a Heat Map control. Here is a link to the site where they list the heat map feature:
http://www.telerik.com/products/wpf/map.aspx
If you are looking to build something, here are a couple blog articles that lay out how to do it (with source provided):
http://www.garrettgirod.com/?p=111
http://www.nickdarnell.com/?p=833
The Syncfusion charting component appears to provide heatmaps.
Not a free component, but if you can get your hands on the Telerik library you could use the following:
http://www.telerik.com/products/wpf/heatmap.aspx
I have had to use it in the past for a few projects and it worked pretty well.
I used DevExpress with a custom ColorFormatter behaviour. I couldn't find anything on the market that did this out of the box. This took me a few days to develop. My code attaached below, hopefully this helps someone out there.
Edit: I used POCO view models and MVVM however you could change this to not use POCO if you desire.
Table2DViewModel.cs
namespace ViewModel
{
[POCOViewModel]
public class Table2DViewModel
{
public ITable2DView Table2DView { get; set; }
public DataTable ItemsTable { get; set; }
public Table2DViewModel()
{
}
public Table2DViewModel(MainViewModel mainViewModel, ITable2DView table2DView) : base(mainViewModel)
{
Table2DView = table2DView;
CreateTable();
}
private void CreateTable()
{
var dt = new DataTable();
var xAxisStrings = new string[]{"X1","X2","X3"};
var yAxisStrings = new string[]{"Y1","Y2","Y3"};
//TODO determine your min, max number for your colours
var minValue = 0;
var maxValue = 100;
Table2DView.SetColorFormatter(minValue,maxValue, null);
//Add the columns
dt.Columns.Add(" ", typeof(string));
foreach (var x in xAxisStrings) dt.Columns.Add(x, typeof(double));
//Add all the values
double z = 0;
for (var y = 0; y < yAxisStrings.Length; y++)
{
var dr = dt.NewRow();
dr[" "] = yAxisStrings[y];
for (var x = 0; x < xAxisStrings.Length; x++)
{
//TODO put your actual values here!
dr[xAxisStrings[x]] = z++; //Add a random values
}
dt.Rows.Add(dr);
}
ItemsTable = dt;
}
public static Table2DViewModel Create(MainViewModel mainViewModel, ITable2DView table2DView)
{
var factory = ViewModelSource.Factory((MainViewModel mainVm, ITable2DView view) => new Table2DViewModel(mainVm, view));
return factory(mainViewModel, table2DView);
}
}
}
ITable2DView.cs
namespace Interfaces
{
public interface ITable2DView
{
void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat);
}
}
Table2DView.xaml.cs
namespace View
{
public partial class Table2DView : ITable2DView
{
public Table2DView()
{
InitializeComponent();
}
static ColorScaleFormat defaultColorScaleFormat = new ColorScaleFormat
{
ColorMin = (Color)ColorConverter.ConvertFromString("#FFF8696B"),
ColorMiddle = (Color)ColorConverter.ConvertFromString("#FFFFEB84"),
ColorMax = (Color)ColorConverter.ConvertFromString("#FF63BE7B")
};
public void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat = null)
{
if (colorScaleFormat == null) colorScaleFormat = defaultColorScaleFormat;
ConditionBehavior.MinValue = minValue;
ConditionBehavior.MaxValue = maxValue;
ConditionBehavior.ColorScaleFormat = colorScaleFormat;
}
}
}
DynamicConditionBehavior.cs
namespace Behaviors
{
public class DynamicConditionBehavior : Behavior<GridControl>
{
GridControl Grid => AssociatedObject;
protected override void OnAttached()
{
base.OnAttached();
Grid.ItemsSourceChanged += OnItemsSourceChanged;
}
protected override void OnDetaching()
{
Grid.ItemsSourceChanged -= OnItemsSourceChanged;
base.OnDetaching();
}
public ColorScaleFormat ColorScaleFormat { get; set;}
public float MinValue { get; set; }
public float MaxValue { get; set; }
private void OnItemsSourceChanged(object sender, EventArgs e)
{
var view = Grid.View as TableView;
if (view == null) return;
view.FormatConditions.Clear();
foreach (var col in Grid.Columns)
{
view.FormatConditions.Add(new ColorScaleFormatCondition
{
MinValue = MinValue,
MaxValue = MaxValue,
FieldName = col.FieldName,
Format = ColorScaleFormat,
});
}
}
}
}
Table2DView.xaml
<UserControl x:Class="View"
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:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
xmlns:ViewModels="clr-namespace:ViewModel"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:behaviors="clr-namespace:Behaviors"
xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
DataContext="{dxmvvm:ViewModelSource Type={x:Type ViewModels:ViewModel}}"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type dxg:GridColumn}">
<Setter Property="Width" Value="50"/>
<Setter Property="HorizontalHeaderContentAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type dxg:HeaderItemsControl}">
<Setter Property="FontWeight" Value="DemiBold"/>
</Style>
</UserControl.Resources>
<!--<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="" Command="{Binding OnLoadedCommand}"/>
</dxmvvm:Interaction.Behaviors>-->
<dxg:GridControl ItemsSource="{Binding ItemsTable}"
AutoGenerateColumns="AddNew"
EnableSmartColumnsGeneration="True">
<dxmvvm:Interaction.Behaviors >
<behaviors:DynamicConditionBehavior x:Name="ConditionBehavior" />
</dxmvvm:Interaction.Behaviors>
<dxg:GridControl.View>
<dxg:TableView ShowGroupPanel="False"
AllowPerPixelScrolling="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</UserControl>
I am creating a search and highlighting the text using the FlowDocumentPageViewer, something similar to the link given.
http://kentb.blogspot.com/2009/06/search-and-highlight-text-in-arbitrary.html
When I search for Tokens of string, (using a list of string) everything works fine and I get my rectangle applied appropriately. But I have two problems here,
When I change the pages of the FlowDocumentPageViewer, my Rectangular highlighted area remains the same and it is not sinking with the Text.
When I zoom in or zoom out of the FlowDocumentPageViewer, the text gets zoomed but the Highlight rectangle remains in the same position,
Can you please help me in resolving this problem such that the rectangle gets applied to the Text itself. I am posing my application here. Please let me know still if you need further information.
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBox x:Name="_searchTextBox" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="20" Margin="2"/>
<Button x:Name="_searchButton" Height="{Binding Height, ElementName=_searchTextBox}" Width="50" Content="GO">
</Button>
<Button x:Name="_listItems" Height="{Binding Height, ElementName=_searchTextBox}" Width="50" Content="List"/>
</StackPanel>
<Grid Grid.Row="1">
<FlowDocumentPageViewer>
<FlowDocument Foreground="Black" FontFamily="Arial">
<Paragraph FontSize="11">
The following details have been details from Amazon to match your initial query.Some of the returned values may have been empty, so have been ommitted from theresults shown here.Also where there have been more than one value returned viathe Amazon Details, these to have beenomitted for the sake of keeping things simplefor this small demo application. Simple is good,when trying to show how something works
</Paragraph>
</FlowDocument>
</FlowDocumentPageViewer>
</Grid>
<ItemsControl ItemsSource="{Binding SearchRectangles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="#99FFFF00" Width="{Binding Width}" Height="{Binding Height}" Tag="{Binding Text}" MouseDown="Rectangle_MouseDown">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="BurlyWood" GlowSize="7"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Grid>
public partial class Window1 : Window
{
public static readonly DependencyProperty SearchTextProperty = DependencyProperty.Register("SearchText",
typeof(string),
typeof(Window1));
public static readonly DependencyProperty SearchRectanglesProperty = DependencyProperty.Register("SearchRectangles",
typeof(ICollection<SearchRectangle>),
typeof(Window1));
public IList<string> SearchTokens { get; set; }
public Window1()
{
InitializeComponent();
SearchRectangles = new ObservableCollection<SearchRectangle>();
_searchButton.Click += delegate
{
DoSearch();
};
_listItems.Click += delegate
{
SearchTokens = new List<string>();
SearchTokens.Add("been");
SearchTokens.Add("Amazon");
SearchTokens.Add("following");
DoSearch(SearchTokens);
};
_searchTextBox.KeyDown += delegate(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
DoSearch();
}
};
}
public void DoSearch(IList<string> searchTokens)
{
SearchRectangles.Clear();
if (searchTokens == null)
return;
foreach (string token in searchTokens)
{
SearchText = token;
DoSearch();
}
}
public string SearchText
{
get { return GetValue(SearchTextProperty) as string; }
set { SetValue(SearchTextProperty, value); }
}
public ICollection<SearchRectangle> SearchRectangles
{
get { return GetValue(SearchRectanglesProperty) as ICollection<SearchRectangle>; }
set { SetValue(SearchRectanglesProperty, value); }
}
private void DoSearch()
{
DoSearch(false);
}
private void DoSearch(bool clearExisting)
{
if( clearExisting == true )
SearchRectangles.Clear();
if (SearchText.Length == 0)
{
return;
}
DoSearch(this);
}
private void DoSearch(DependencyObject searchIn)
{
if (searchIn == null)
{
return;
}
var contentHost = searchIn as IContentHost;
if (contentHost != null)
{
DoSearch(contentHost as UIElement, contentHost);
}
else
{
var documentViewerBase = searchIn as DocumentViewerBase;
if (documentViewerBase != null)
{
//extract the content hosts from the document viewer
foreach (var pageView in documentViewerBase.PageViews)
{
contentHost = pageView.DocumentPage as IContentHost;
if (contentHost != null)
{
DoSearch(documentViewerBase, contentHost);
}
}
}
}
//recurse through children
var childCount = VisualTreeHelper.GetChildrenCount(searchIn);
for (var i = 0; i < childCount; ++i)
{
DoSearch(VisualTreeHelper.GetChild(searchIn, i));
}
}
private void DoSearch(UIElement uiHost, IContentHost contentHost)
{
if (uiHost == null)
{
return;
}
var textBlock = contentHost as TextBlock;
if (textBlock != null)
{
//this has the side affect of converting any plain string content in the TextBlock into a hosted Run element
//that's bad in that it is unexpected, but good in that it allows us to access the hosted elements in a
//consistent fashion below, rather than special-casing TextBlocks with text only content
var contentStart = textBlock.ContentStart;
}
var hostedElements = contentHost.HostedElements;
while (hostedElements.MoveNext())
{
var run = hostedElements.Current as Run;
if (run != null && !string.IsNullOrEmpty(run.Text))
{
ApplyHighlighting(run.Text, delegate(int start, int length)
{
var textPointer = run.ContentStart;
textPointer = textPointer.GetPositionAtOffset(start, LogicalDirection.Forward);
var leftRectangle = textPointer.GetCharacterRect(LogicalDirection.Forward);
textPointer = textPointer.GetPositionAtOffset(length, LogicalDirection.Forward);
var rightRectangle = textPointer.GetCharacterRect(LogicalDirection.Backward);
var rect = new Rect(leftRectangle.TopLeft, rightRectangle.BottomRight);
var translatedPoint = uiHost.TranslatePoint(new Point(0, 0), null);
rect.Offset(translatedPoint.X, translatedPoint.Y);
return rect;
});
}
}
}
private void ApplyHighlighting(string text, Func<int, int, Rect> getRectHandler)
{
var currentIndex = 0;
while (true)
{
var index = text.IndexOf(SearchText, currentIndex, StringComparison.CurrentCultureIgnoreCase);
if (index == -1)
{
return;
}
var rect = getRectHandler(index, SearchText.Length);
if (rect != Rect.Empty)
{
SearchRectangles.Add(new SearchRectangle(rect.Top, rect.Left, rect.Width, rect.Height,SearchText));
}
currentIndex = index + SearchText.Length;
}
}
private void Rectangle_MouseDown(object sender, MouseEventArgs e)
{
Rectangle r = sender as Rectangle;
MessageBox.Show(r.Tag.ToString());
}
private void FlowDocumentPageViewer_PageViewsChanged(object sender, EventArgs e)
{
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
DoSearch(SearchTokens);
}
}
public class SearchRectangle
{
private readonly double _top;
private readonly double _left;
private readonly double _width;
private readonly double _height;
private readonly string _text;
public SearchRectangle(double top, double left, double width, double height,string text)
{
_top = top;
_left = left;
_width = width;
_height = height;
_text = text;
}
public string Text
{
get { return _text; }
}
public double Top
{
get { return _top; }
}
public double Left
{
get { return _left; }
}
public double Width
{
get { return _width; }
}
public double Height
{
get { return _height; }
}
}
Best,
Bala.
What would appear to be an easy solution is to actually edit the document to do the hilighting as you search, remembering to edit it back when you clear the search results or need the unedited document for other purposes (such as to save it).
You can search a document for text using the code I posted in this answer. In your case, instead of setting the selection to each range found, you'll want to hilight the range like this:
public void HilightMatches(TextRange searchRange, string searchText)
{
var start = searchRange.Start;
while(start != searchRange.End)
{
var foundRange = FindTextInRange(
new TextRange(start, searchRange.End),
searchText);
if(foundRange==null) return;
// Here is the meat...
foundRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
start = foundRange.End;
}
}
To complete the picture, you'll need a way to clear the hilighting when you're done. Several options:
Save the ranges that were hilighted so you can just use ApplyPropertyValue to apply Brushes.Transparent background (simple, but this will erase any background that was previously set)
Split each range that was found into runs and save the lists of runs each with its original background color, so it will be easy to restore
Save the entire document before you muck with it and just restore it to remove the hilighting
If the document is in a RichTextBox and the user won't be allowed to edit the document, you can simply use the undo mechanism. Make sure your TextBoxBase.UndoLimit is increased as you do the hilighting, then to remove the hilighting just call TextBoxBase.Undo() once per hilight.
Try to this solution
http://rredcat.blogspot.com/2009/11/zoom-and-page-chahged-events-for.html