I have a simple search text box. This text box acts as a filter. I've now copied/pasted the code for the 5th time and enough is enough. Time for a custom control.
left and right brackets have been replaced with ()
My custom control will be simple. My problem is I want to have a dependencyProperty on this control that is of type List(T).
I created a test project to proof it out and make sure it works. It works well. Ignore List.
Below is the entire class. The problem is that the only thing holding me up is replacing List (Person) with List(T). Something like List where: T is Object
typeof(List(T) where: T is Object) <= Obviously I can't do that but gives an idea what I'm trying to accomplish.
public class SearchTextBox : TextBox
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("FilterSource", typeof(List<Person>), typeof(SearchTextBox), new UIPropertyMetadata(null)); //I WANT THIS TO BE LIST<T>
public List<Person> FilterSource
{
get
{
return (List<Person>)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public static readonly DependencyProperty FilterPropertyNameProperty =
DependencyProperty.Register("FilterPropertyName", typeof(String), typeof(SearchTextBox), new UIPropertyMetadata());
public String FilterPropertyName
{
get
{
return (String)GetValue(FilterPropertyNameProperty);
}
set
{
SetValue(FilterPropertyNameProperty, value);
}
}
public SearchTextBox()
{
this.KeyUp += new System.Windows.Input.KeyEventHandler(SearchBox_KeyUp);
}
void SearchBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(FilterSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterTheSource);
}
bool FilterTheSource(object obj)
{
if (obj == null) return false;
Type t = obj.GetType();
PropertyInfo pi = t.GetProperty(FilterPropertyName);
//object o = obj.GetType().GetProperty(FilterPropertyName);
String propertyValue = obj.GetType().GetProperty(FilterPropertyName).GetValue(obj, null).ToString().ToLower();
if (propertyValue.Contains(this.Text.ToLower()))
{
return true;
}
return false;
}
}
No; that's not possible.
Instead, just use the non-generic typeof(IList).
Related
I have a UserControl that has an ObservableCollection dependency property with a property changed callback. The callback rebuilds the nodes in a TreeView control. This all works fine but I would like to be able to have design data. Unfortunately, neither the constructor, the callback nor a default value function call is called by the designer unless I embed my control in another. Is there a way loading default data in this scenario?
Below is the code behind for my control
public partial class ScheduleResourcesSummaryTreeView : UserControl
{
public ObservableCollection<PerformanceProjection> SelectedPerformances
{
get { return (ObservableCollection<PerformanceProjection>)GetValue(SelectedPerformancesProperty); }
set { SetValue(SelectedPerformancesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPerformancesProperty =
DependencyProperty.Register("SelectedPerformances",
typeof(ObservableCollection<PerformanceProjection>),
typeof(ScheduleResourcesSummaryTreeView),
new FrameworkPropertyMetadata(
new ObservableCollection<PerformanceProjection>(),
FrameworkPropertyMetadataOptions.AffectsRender, SelectedPerformancesChanged)
);
private static void SelectedPerformancesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ScheduleResourcesSummaryTreeView resourcesTree)) return;
resourcesTree.ResourcesTree.Items.Clear();
var sessions = e.NewValue as ObservableCollection<PerformanceProjection> ?? new ObservableCollection<PerformanceProjection>();
if (sessions.Count == 0) return;
var projections = sessions.SelectMany(x => x.ResourceBookingProjections);
TreeNode<ResourceBookingProjection> resourceBookingTreeRoot = new RvtSummaryTree(new ObservableCollection<ResourceBookingProjection>(projections), "");
foreach (var treeNode in resourceBookingTreeRoot.Children)
{
resourcesTree.ResourcesTree.Items.Add((TreeNode<ResourceBookingProjection>)treeNode);
}
}
private static ObservableCollection<PerformanceProjection> DefaultCollection()
{
var prop = DesignerProperties.IsInDesignModeProperty;
var designMode = (bool) DependencyPropertyDescriptor
.FromProperty(prop, typeof(FrameworkElement))
.Metadata.DefaultValue;
if (!designMode)
return new ObservableCollection<PerformanceProjection>();
var designdata = new PerformanceProjectionsViewModelDesignData();
return designdata.SelectedPerformances;
}
public ScheduleResourcesSummaryTreeView()
{
InitializeComponent();
}
}
I would the rows containing the search value (Search Name), show this value (in datagrid) with a different color.
See the pic below.
Some ideas about this ?
You can do this by creating a new control that extends a standard TextBlock, which uses a series of Run items to display the text, using the appropriate formatting.
public class HighlightTextBlock: TextBlock
{
public string BaseText
{
get { return (string)GetValue(BaseTextProperty); }
set { SetValue(BaseTextProperty, value); }
}
public static readonly DependencyProperty BaseTextProperty =
DependencyProperty.Register("BaseText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public string HighlightText
{
get { return (string)GetValue(HighlightTextProperty); }
set { SetValue(HighlightTextProperty, value); }
}
public static readonly DependencyProperty HighlightTextProperty =
DependencyProperty.Register("HighlightText", typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(null, UpdateDisplay));
public Brush HighlightBrush
{
get { return (Brush)GetValue(HighlightBrushProperty); }
set { SetValue(HighlightBrushProperty, value); }
}
public static readonly DependencyProperty HighlightBrushProperty =
DependencyProperty.Register("HighlightBrush", typeof(Brush), typeof(HighlightTextBlock), new PropertyMetadata(Brushes.Orange, UpdateDisplay));
public bool HighlightCaseSensitive
{
get { return (bool)GetValue(HighlightCaseSensitiveProperty); }
set { SetValue(HighlightCaseSensitiveProperty, value); }
}
public static readonly DependencyProperty HighlightCaseSensitiveProperty =
DependencyProperty.Register("HighlightCaseSensitive", typeof(bool), typeof(HighlightTextBlock), new PropertyMetadata(false, UpdateDisplay));
private static void UpdateDisplay(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var hightlightTextBlock = sender as HighlightTextBlock;
if (hightlightTextBlock == null)
return;
hightlightTextBlock.Inlines.Clear();
if (string.IsNullOrEmpty(hightlightTextBlock.BaseText))
return;
if (string.IsNullOrEmpty(hightlightTextBlock.HighlightText))
{
hightlightTextBlock.Inlines.Add(new Run(hightlightTextBlock.BaseText));
return;
}
var textItems = Regex.Split(hightlightTextBlock.BaseText,
"(" + hightlightTextBlock.HighlightText + ")",
hightlightTextBlock.HighlightCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase);
foreach (var item in textItems)
{
var run = new Run(item);
var highlight = hightlightTextBlock.HighlightCaseSensitive
? string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCulture) == 0
: string.Compare(item, hightlightTextBlock.HighlightText, StringComparison.InvariantCultureIgnoreCase) == 0;
if (highlight)
run.Background = hightlightTextBlock.HighlightBrush;
hightlightTextBlock.Inlines.Add(run);
}
}
}
The brackets around the HighlightText value tells Regex.Split to include the matched text in the returned list of items.
This control can then be used as part of an item template in your datagrid column definition. See here for an example of how to do that.
I'm trying to set the dependency property "WordPad" from within the Inline event of MouseEnter from a CustomTextBlock. But this error results:
An object reference is required for the non-static field, method, or property 'WpfCustomControlLibrary.CustomTextBlock.WordPad.get'
How can I achieve this?
Any help would be most appreciated. Thanks.
Given the following class:
public class CustomTextBlock : TextBlock
{
public string InLineText
{
get { return (string)GetValue(InLineTextProperty); }
set { SetValue(InLineTextProperty, value); }
}
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) =>
{
//PropertyChangedCallback
CustomTextBlock tb = (CustomTextBlock)o;
string text = (string)e.NewValue;
tb.Inlines.Clear();
if (String.IsNullOrEmpty(text))
return;
List<Inline> inlines = new List<Inline>();
string[] words = Regex.Split(text, #"(\s+)");
Inline inline = null;
foreach (string s in words)
{
Run run = new Run(s);
inline = run;
inline.MouseEnter += new System.Windows.Input.MouseEventHandler(inline_MouseEnter);
inline.MouseLeave += new System.Windows.Input.MouseEventHandler(inline_MouseLeave);
tb.Inlines.Add(inline);
}
}));
public WritingPad WordPad
{
get { return (WritingPad)GetValue(WordPadProperty); }
set { SetValue(WordPadProperty, value); }
}
public static readonly DependencyProperty WordPadProperty =
DependencyProperty.Register("WordPad", typeof(WritingPad), typeof(CustomTextBlock), new UIPropertyMetadata(null));
static void inline_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Run Sender = sender as Run;
TextPointer tp0 = Sender.ContentStart;
TextPointer tp1 = Sender.ContentEnd;
Rect StartRect = tp0.GetCharacterRect(LogicalDirection.Forward);
Rect EndRect = tp1.GetCharacterRect(LogicalDirection.Backward);
StartRect.Union(EndRect);
WordPad = new WritingPad(); <--**THIS FAILS ????
}
Make the inline_MouseEnter and inline_MouseLeave methods non-static and attach them to your Runs like this:
inline.MouseEnter += tb.inline_MouseEnter;
inline.MouseLeave += tb.inline_MouseLeave;
Even better would be to make the whole PropertyChangedCallback non-static and then write the dependency property declaration like this:
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) => ((CustomTextBlock)o).InlineTextChanged((string)e.NewValue)));
private void InlineTextChanged(string text)
{
Inlines.Clear();
if (!string.IsNullOrEmpty(text))
{
foreach (string s in Regex.Split(text, #"(\s+)"))
{
var run = new Run(s);
run.MouseEnter += inline_MouseEnter;
run.MouseLeave += inline_MouseLeave;
Inlines.Add(run);
}
}
}
I'm hopeful a guru can give a better solution.
It should be noted that Run has a "Parent" property which resolved to this instance of CustomTextBlock. Hence, this seems to work,
CustomTextBlock ctb = Sender.Parent as CustomTextBlock;
ctb.WordPad = new WritingPad
{
WordBounds = StartRect,
WritingPadMode = Enumerations.WritingPadModes.EditingByWord
};
im exploring WPF world, i find a great example on the web about how to use binding on xml
http://www.codeproject.com/Articles/37854/How-to-Perform-WPF-Data-Binding-Using-LINQ-to-XML
Now im trying to extends this example: i want to create a "class in the middle" between the XElement and the UI and bind all togheder in a chain so, if i have a modification into the xml, then i have the property in the middle class updated then the UI updated too.
Here some code:
This is the class that wrap the XElement
public class XElementDataProvider : ObjectDataProvider
{
public XElementDataProvider()
{
ObjectInstance = XElement.Load(#"C:\MyFile.xml");
}
private static XElementDataProvider instance;
public static XElementDataProvider Instance
{
get
{
if (instance == null)
{
instance = new XElementDataProvider();
}
return instance;
}
}
}
This is the MiddleClass
public class MiddleClass : DependencyObject
{
XElementDataProvider xElementDataProvider;
XElement myxml;
public MiddleClass()
{
//here i get my dataprovider
xElementDataProvider = XElementDataProvider.Instance;
myxml = xElementDataProvider.Data as XElement;
//i bind my internal collection to the Elements...
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default//here i cant use TwoWay, give me //back an exeption
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
//just to have confirmation of the adding
myxml.Changed += new EventHandler<XObjectChangeEventArgs (myxml_Changed);
}
void myxml_Changed(object sender, XObjectChangeEventArgs e)
{
}
//i use a DependencyProperty to have also a change callback
public static readonly DependencyProperty XBookListProperty =
DependencyProperty.Register("XBookList", typeof(IEnumerable),
typeof(MiddleClass),
new PropertyMetadata(XBookPropertyChanged)
);
//here i have a notification only at start but no when i add a new book
private static void XBookPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MiddleClass middleClass = d as MiddleClass;
middleClass.XBookPropertyChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void XBookPropertyChanged(IEnumerable old, IEnumerable newValue)
{
}
//this is the propery i finally want to expose to the UI but im not able //to keep updated
public List<Book> bookList;
public List<Book> BookList
{
get
{
return bookList;
}
set
{
bookList = value;
}
}
//this is my internal list binded to the xml
private IEnumerable XBookList
{
get
{
return (IEnumerable)GetValue(XBookListProperty);
}
set
{
SetValue(XBookListProperty, value);
}
}
//here i try to add a book addind direcly to the xml//i expect a //notification of propery changed...but nothing
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
return true;
}
Book is a class thar repersents a book, let say it has only a name propery for now.
The UI class misses but it should bind on public List<Book> BookList and show books names to the user in a ListBox
Enyone knows why i dont recive any notification...or what i have to do to keep the public List<Book> BookList synchronized with private IEnumerable<XBookList>?
OK, after many attempts, the only solution I found is this one:
to have notifications when something changes in the IEnumerable<XBookList> you need to clear it ad rebind after you modify it.
In this way you have a first, not used notification, about the clear and then another notification about the new set.
Then in the handler you can synchronize the new list with the old one.
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
ClearValue(XBookListProperty);
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
return true;
}
This might sound like a strange request and i'm not sure if it's actually possible, but I have a Silverlight DataPager control where it says "Page 1 of X" and I want to change the "Page" text to say something different.
Can this be done?
In DataPager style there is a part by name CurrentPagePrefixTextBlock by default its value is "Page".
You can refer http://msdn.microsoft.com/en-us/library/dd894495(v=vs.95).aspx for more info.
One of the solution is to extend DataPager
Here is the code to do that
public class CustomDataPager:DataPager
{
public static readonly DependencyProperty NewTextProperty = DependencyProperty.Register(
"NewText",
typeof(string),
typeof(CustomDataPager),
new PropertyMetadata(OnNewTextPropertyChanged));
private static void OnNewTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var newValue = (string)e.NewValue;
if ((sender as CustomDataPager).CustomCurrentPagePrefixTextBlock != null)
{
(sender as CustomDataPager).CustomCurrentPagePrefixTextBlock.Text = newValue;
}
}
public string NewText
{
get { return (string)GetValue(NewTextProperty); }
set { SetValue(NewTextProperty, value); }
}
private TextBlock _customCurrentPagePrefixTextBlock;
internal TextBlock CustomCurrentPagePrefixTextBlock
{
get
{
return _customCurrentPagePrefixTextBlock;
}
private set
{
_customCurrentPagePrefixTextBlock = value;
}
}
public CustomDataPager()
{
this.DefaultStyleKey = typeof(DataPager);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CustomCurrentPagePrefixTextBlock = GetTemplateChild("CurrentPagePrefixTextBlock") as TextBlock;
if (NewText != null)
{
CustomCurrentPagePrefixTextBlock.Text = NewText;
}
}
}
Now by setting NewText property in this CustomDataPager we can get whatever text we want instead of "Page"
xmlns:local="clr-namespace:Assembly which contains CustomDataPager"
<local:CustomDataPager x:Name="dataPager1"
PageSize="5"
AutoEllipsis="True"
NumericButtonCount="3"
DisplayMode="PreviousNext"
IsTotalItemCountFixed="True" NewText="My Text" />
Now it displays "My Text" Instead of "Page".
But other parts also need to be customised inorder make this work correctly!!
Hope this answers your question