Using Command Links in Winforms .NET Core - c#

I would like to add command links into my C# .NET Core Windows Forms app for Windows 10 21H2, but I haven't found any tutorials online.
I believe that this is a photo of a command link, but I'm not too sure. Can someone tell me how to add these to my app?
I couldn't find any controls like this in the VS toolbox.

Try this Custom Control, derived from Button, where the BS_COMMANDLINK Style is added in the CreateParams property override.
This turns the standard Button Control in a CommandLink Button.
The Text Property value of the Control is shown to the right of the arrow, while the custom LinkNote Property provides the description that is shown right below the text, with a smaller Font.
When you set the LinkNote value, the BCM_SETNOTE message is sent to the native Button. This sets the associated Note, at design-time or run-time.
The custom ControlDesigner is used to remove properties that are not needed / used when this style is set, or properties, as FlatStyle (which needs to be set to FlatStyle.System), that cannot be changed.
Unfortunately, this Designer works only in .NET Framework, to make it work in .NET 5+, you need to install the Microsoft.WinForms.Designer.SDK package.
Open up the Package Manager Console and paste:
NuGet\Install-Package Microsoft.WinForms.Designer.SDK -Version 1.1.0-prerelease-preview3.22076.5
This package is hard to find otherwise.
If you don't care about the Designer's features, just remove it
using System.Collections;
using System.ComponentModel;
using System.Runtime.InteropServices;
#if NET5_0_OR_GREATER
using Microsoft.DotNet.DesignTools.Designers;
#else
using System.Windows.Forms.Design;
#endif
[ToolboxItem(true)]
[Designer(typeof(CommandLinkDesigner))]
public class CommandLink : Button {
private const int BCM_SETNOTE = 0x1609;
private string m_LinkNote = string.Empty;
public CommandLink() {
SetStyle(ControlStyles.ResizeRedraw | ControlStyles.UseTextForAccessibility, true);
FlatStyle = FlatStyle.System;
}
[DefaultValue(FlatStyle.System)]
public new FlatStyle FlatStyle {
get { return base.FlatStyle; }
set { base.FlatStyle = FlatStyle.System; }
}
public string LinkNote {
get => m_LinkNote;
set {
if (value != m_LinkNote) {
m_LinkNote = value;
SendMessage(this.Handle, BCM_SETNOTE, 0, m_LinkNote);
}
}
}
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
cp.Style |= (0 | 0x0E); // Set BS_COMMANDLINK
return cp;
}
}
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern bool SendMessage(IntPtr hWnd, int msg, int wParam, string lParam);
public class CommandLinkDesigner : ControlDesigner {
private readonly string[] RemovedProperties = new[] {
"AllowDrop", "AutoEllipsis", "AutoSize", "AutoSizeMode",
"BackColor", "BackgroundImage", "BackgroundImageLayout",
"Cursor", "FlatAppearance", "FlatStyle",
"ForeColor", "Image", "ImageAlign", "ImageImdex", "ImageKey",
"ImageList", "TextAlign", "TextImageRelation"
};
public CommandLinkDesigner() { }
protected override void PreFilterProperties(IDictionary properties)
{
foreach (string prop in RemovedProperties) {
properties.Remove(prop);
}
base.PreFilterProperties(properties);
}
}
}

Related

Prevent inserting special characters on Usercontrol (textbox) by Copy Paste

I have to validate a Usercontrol (textbox) from inserting special characters. In keypress event I use this code to handle this.
OnKeyPress Overwrite:
protected override void OnKeyPress(KeyPressEventArgs e) {
base.OnKeyPress(e);
MethodToPrevent(e);
}
MethodToPrevent Function:
private void MethodToPrevent(KeyPressEventArgs e) {
var regex = new Regex(#"[^a-zA-Z0-9\s]");
if (regex.IsMatch(e.KeyChar.ToString())) {
e.Handled = true;
}
}
Now this is working fine. But if user copy paste the string with special characters this is not working. How can I handle that?
Tried
protected override void OnTextChanged(EventArgs args)
But can not catch the Sender part here.
Actualy your code prevent to ctrl, but it not prevent to mouse right click and paste. Maybe you can prevent mouse right click or you can use TextChanged event.
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
base.OnKeyPress(e);
MethodToPrevent(e);
}
private void MethodToPrevent(KeyPressEventArgs e)
{
var regex = new Regex(#"[^a-zA-Z0-9\s]");
if (regex.IsMatch(e.KeyChar.ToString()))
{
e.Handled = true;
}
}
// If the user presses the wrong key, it is already blocked with the
//MethodToPrevent() before the TextChanged().
//it is only prevent mouse right click and paste
private void textBox1_TextChanged(object sender, EventArgs e)
{
var regex = new Regex(#"[^a-zA-Z0-9\s]");
if (regex.IsMatch(textBox1.Text.ToString()))
{
textBox1.Text = "";
}
}
Since you're apparently using a Custom Control (TextBox derived), some suggestions to handle the User Paste actions and filtering text edits in your Control (the filter part is what you provided, more work is required in this department to make this bullet-proof - not just in relation to error handling).
This Custom Control adds some features to the standard TextBox:
Filters chars handled by OnKeyPress, allowing cursor movements, Delete an BackSpace (I've added \b to the filter Regex, I think it was missing).
Filters WM_PASTE events in 3 different ways, using the PasteAction Enum, linked to the Public UserPaste Property, which defines the Control behavior in reaction to a Paste operation (modify as required):
Allow: a User can paste anything inside the TextBox
Partial: a User can paste only what the Regex filter allows, the rest is removed
Disallow: a User cannot paste anything
Has an option to allow Numbers only, at the base class level (no Regex). This is coupled by the feedback provided by the ErrorProvider class.
To allow the partial paste feature, the WndProc override intercepts WM_PASTE, filters the Text read from the Clipboard, using the Clipboard.GetText() method (with TextDataFormat.UnicodeText), then sends a EM_REPLACESEL message to the edit control, to add the modified Text (to the User, it appears as an actual paste operation).
► base.WndProc() is not called in any case and the Clipboard is untouched.
→ If you mean to notify a User of the action taken, please don't show a MessageBox in the WndProc method override.
Note: this is a modified version of the Custom Control I've already posted here (with some other methods that may come in handy), for the same exact matter. → If the two questions are actually related, let me know.
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Windows.Forms;
[ToolboxItem(true)]
[DesignerCategory("Code")]
public class TextBoxEx : TextBox
{
private bool m_NumbersOnly = false;
private Regex regex = new Regex(#"[^a-zA-Z0-9\s\b]", RegexOptions.Compiled);
public TextBoxEx() { }
public enum PasteAction
{
Allow,
Disallow,
Partial
}
public PasteAction UserPaste { get; set; }
public override string Text {
get => base.Text;
set {
if (!base.Text.Equals(value)) {
base.Text = regex.Replace(value, "");
}
}
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (regex.IsMatch(e.KeyChar.ToString())) {
e.Handled = true;
}
base.OnKeyPress(e);
}
protected override CreateParams CreateParams
{
[SecurityPermission(SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.UnmanagedCode)]
get {
CreateParams cp = base.CreateParams;
if (m_NumbersOnly) {
cp.Style |= NativeMethods.ES_NUMBER;
}
else {
cp.Style &= ~NativeMethods.ES_NUMBER;
}
return cp;
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg) {
case NativeMethods.WM_PASTE:
switch (UserPaste) {
case PasteAction.Disallow:
return;
case PasteAction.Partial:
string text = Clipboard.GetText(TextDataFormat.UnicodeText);
text = regex.Replace(text, "");
NativeMethods.SendMessage(this.Handle, NativeMethods.EM_REPLACESEL, 1, text);
return;
case PasteAction.Allow:
break;
}
break;
}
base.WndProc(ref m);
}
private class NativeMethods
{
internal const int WM_PASTE = 0x0302;
internal const int ES_NUMBER = 0x2000;
internal const int EM_REPLACESEL = 0xC2;
[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, string lParam);
}
}

Xamarin forms UWP tooltips

I would like to add tooltips in Xamarin for UWP by using a native view and the Windows.UI.Xaml Nuget package. I have added the following reference to the xaml page to get the windows native view:
<ContentPage
xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
</ContentPage>
I can successfully access native windows controls, e.g. a TextBlock:
< win: TextBlock Text="S"/>
However, when I try to add the tooltip to the control:
<win:TextBlock Text="S" ToolTipService.ToolTip="Service agreement"/>
I get the following exception during compilation:
"Xamarin.Forms.Xaml.XamlParseException: 'Position 56:45. Type
ToolTipService not found in xmlns
http://xamarin.com/schemas/2014/forms'"
Is the system getting confused between the standard Windows.UI.Xaml.Controls namespace and the extended one from the Nuget package?
ToolTipService is attached property, and it will not find ToolTipService assembly in Forms client, the better way is use Effect to create your own tip service and render it in UWP platform. You could refer the following steps.
Create a subclass of the PlatformEffect class.
Override the OnAttached method and write logic to customize the control.
Override the OnDetached method and write logic to clean up the control customization, if required.
Add a ResolutionGroupName attribute to the effect class. This attribute sets a company wide namespace for effects, preventing collisions with other effects with the same name. Note that this attribute can only be applied once per project.
Add an ExportEffect attribute to the effect class. This attribute registers the effect with a unique ID that's used by Xamarin.Forms, along with the group name, to locate the effect prior to applying it to a control. The attribute takes two parameters – the type name of the effect, and a unique string that will be used to locate the effect prior to applying it to a control.
UWP Code Part
[assembly: ResolutionGroupName("Microsoft")]
[assembly: ExportEffect(typeof(UWPToolTipEffect), nameof(TooltipEffect))]
namespace NativeSwitch.UWP
{
public class UWPToolTipEffect : PlatformEffect
{
protected override void OnAttached()
{
var control = Control ?? Container;
if (control is DependencyObject)
{
ToolTip toolTip = new ToolTip();
toolTip.Content = TooltipEffect.GetText(Element);
switch (TooltipEffect.GetPosition(Element))
{
case TooltipPosition.Bottom:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Bottom;
break;
case TooltipPosition.Top:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Top;
break;
case TooltipPosition.Left:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Left;
break;
case TooltipPosition.Right:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Right;
break;
default:
return;
}
ToolTipService.SetToolTip(control, toolTip);
}
}
protected override void OnDetached()
{
}
}
}
Forms code part
public static class TooltipEffect
{
public static readonly BindableProperty TextProperty =
BindableProperty.CreateAttached("Text", typeof(string), typeof(TooltipEffect), string.Empty, propertyChanged: OnTextChanged);
public static readonly BindableProperty PositionProperty =
BindableProperty.CreateAttached("Position", typeof(TooltipPosition), typeof(TooltipEffect), TooltipPosition.Bottom);
public static string GetText(BindableObject view)
{
return (string)view.GetValue(TextProperty);
}
public static void SetText(BindableObject view, string value)
{
view.SetValue(TextProperty, value);
}
public static TooltipPosition GetPosition(BindableObject view)
{
return (TooltipPosition)view.GetValue(PositionProperty);
}
public static void SetPosition(BindableObject view, TooltipPosition value)
{
view.SetValue(PositionProperty, value);
}
static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
string text = (string)newValue;
if (!string.IsNullOrEmpty(text))
{
view.Effects.Add(new ControlTooltipEffect());
}
else
{
var toRemove = view.Effects.FirstOrDefault(e => e is ControlTooltipEffect);
if (toRemove != null)
{
view.Effects.Remove(toRemove);
}
}
}
}
public enum TooltipPosition
{
Bottom,
Right,
Left,
Top
}
class ControlTooltipEffect : RoutingEffect
{
public ControlTooltipEffect() : base($"Microsoft.{nameof(TooltipEffect)}")
{
}
}
Usage
// forms project namesapce
xmlns:effects="clr-namespace:ToolTipTestApp"
......
<win:TextBlock
effects:TooltipEffect.Position="Right"
effects:TooltipEffect.Text="Hello"
Text="Hello Wrold"
/>

How to draw border for MaskedTextBox using custom color?

I am trying to create a masked textbox to have a border colors.
I tried this below code to achieve it:
public class MaskedTextBoxWithBorder : UserControl
{
MaskedTextBox maskedtextBox;
public MaskedTextBoxWithBorder()
{
maskedtextBox = new MaskedTextBox()
{
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(-1, -1),
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right
};
Control container = new ContainerControl()
{
Dock = DockStyle.Fill,
Padding = new Padding(-1)
};
container.Controls.Add(maskedtextBox);
this.Controls.Add(container);
DefaultBorderColor = SystemColors.ControlDark;
FocusedBorderColor = Color.Red;
BackColor = DefaultBorderColor;
Padding = new Padding(1);
Size = maskedtextBox.Size;
}
public Color DefaultBorderColor { get; set; }
public Color FocusedBorderColor { get; set; }
public override string Text
{
get
{
return maskedtextBox.Text;
}
set
{
maskedtextBox.Text = value;
}
}
protected override void OnEnter(EventArgs e)
{
BackColor = FocusedBorderColor;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e)
{
BackColor = DefaultBorderColor;
base.OnLeave(e);
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
base.SetBoundsCore(x, y, width, maskedtextBox.PreferredHeight, specified);
}
}
But the problem is it does not have the all features of the masked text box like setting mask type etc.
So I changed my code like this:
public class MaskedTextBoxWithBorder : UserControl
Now I have all features of Masked text box but border colors are not affected.
Is there any way to extend Masked textbox to get border style without losing features something like this which is not possible.
public class MaskedTextBoxWithBorder : UserControl, MaskedTestBox
To draw border of MaskedTextBox you should override WndProc and handle WM_NCPAINT message. Then get the window dc of the control and create a Graphics object from that dc, then draw border for control.This solution has been used also in ToolStripTextBox. The solution can be applied also on a TextBox.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyMaskedTextBox : MaskedTextBox
{
public const int WM_NCPAINT = 0x85;
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NCPAINT)
{
var hdc = GetWindowDC(this.Handle);
using (var g = Graphics.FromHdcInternal(hdc))
{
g.DrawRectangle(Pens.Blue, new Rectangle(0, 0, Width - 1, Height - 1));
}
ReleaseDC(this.Handle, hdc);
}
}
}
TextBox and MaskedTextBox controls are only wrappers of Win32 TextBox controls so owner drawing them (for custom borders, overlays or anything else) is a bit more complex than normally. Here's what you should do to achieve what you're trying to do.
Derive from MaskedTextBox: public class MaskedTextBoxWithBorder : MaskedTextBox
Get access to the message stream for the Win32 TextBox (it draws itself in response to multiple messages so you need to catch them all, not just the standard WM_PAINT message).
Get the handle to the device context and transform it into a managed Graphics object to draw the border.
Take a look at the following article that explains each step in detail: Adding an Icon or Control to a TextBox or ComboBox
Even though the article discusses the basic TextBox control, it doesn't matter. Both TextBox and MaskedTextBox derive from the TextBoxBase class, which implements all the important parts we're interested in.
Well, usually in a given application, you only change a few property so you might simply add some extra property to your user control for those properties you want to be able to change.
public class MaskedTextBoxWithBorder : UserControl
{
MaskedTextBox maskedtextBox;
// Other existing code...
public string Mask
{
get { return maskedtextBox.Mask; }
set { maskedtextBox.Mask = value; }
}
// Do same thing for other properties you want to change...
}
If you really want to change a lot of properties, then other solutions might be more appropriate...

Using custom color picker dialog in PropertyGrid

In PropertyGrid default color picker dialog not allow to set alpha value of color.
I already made my own color picker dialog and want to use it in PropertyGrid but not sure how to do it.
I managed to use my custom color picker dialog in property grid and copying code of it here in case some need it too:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace HelpersLib
{
public class MyColorEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (value.GetType() != typeof(RGBA))
{
return value;
}
IWindowsFormsEditorService svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (svc != null)
{
using (DialogColor form = new DialogColor((RGBA)value))
{
if (svc.ShowDialog(form) == DialogResult.OK)
{
return form.NewColor.RGBA;
}
}
}
return value;
}
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return true;
}
public override void PaintValue(PaintValueEventArgs e)
{
using (SolidBrush brush = new SolidBrush((RGBA)e.Value))
{
e.Graphics.FillRectangle(brush, e.Bounds);
}
e.Graphics.DrawRectangleProper(Pens.Black, e.Bounds);
}
}
}
And this is how it looks in property grid:
When i click button of it, it will open custom color dialog.
But still have one problem which i can't solve.
I can't use Color struct with this UITypeEditor, therefore created RGBA class.
When i use color struct, it look like this:
I will open another question for it i guess: Custom ColorEditor does not work properly on Color struct
To interact with PropertyGrid, you have to create your own "property class" (as described here). You can customise different parts and thus there are multiple solutions for what you want. As a first approach to your problem, here you have a code for propertyGrid1:
Property curProperty = new Property();
propertyGrid1.SelectedObject = curProperty;
Where Property is defined by:
public class Property
{
private ColorDialog _dialog = new customColorDialogDialog();
public ColorDialog dialog
{
get { return _dialog; }
set { _dialog.ShowDialog(); }
}
}
class customColorDialogDialog : ColorDialog
{
}
In this code, your color dialog (customColorDialogDialog) is triggered when clicking on the cell on the right hand side of the property name ("dialog").

How can I remove the selection border on a ListViewItem

I'm using SetWindowTheme and SendMessage to make a .net listview look like a vista style listview, but the .net control still has a dotted selection border around the selected item:
Selected items in the explorer listview don't have that border around them. How can I remove it?
Windows Explorer:
Edit: Solution:
public static int MAKELONG(int wLow, int wHigh)
{
int low = (int)LOWORD(wLow);
short high = LOWORD(wHigh);
int product = 0x00010000 * (int)high;
int makeLong = (int)(low | product);
return makeLong;
}
SendMessage(olv.Handle, WM_CHANGEUISTATE, Program.MAKELONG(UIS_SET, UISF_HIDEFOCUS), 0);
Telanors solution worked for me. Here's a slightly more self-contained version.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyListView : ListView
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private const int WM_CHANGEUISTATE = 0x127;
private const int UIS_SET = 1;
private const int UISF_HIDEFOCUS = 0x1;
public MyListView()
{
this.View = View.Details;
this.FullRowSelect = true;
// removes the ugly dotted line around focused item
SendMessage(this.Handle, WM_CHANGEUISTATE, MakeLong(UIS_SET, UISF_HIDEFOCUS), 0);
}
private int MakeLong(int wLow, int wHigh)
{
int low = (int)IntLoWord(wLow);
short high = IntLoWord(wHigh);
int product = 0x10000 * (int)high;
int mkLong = (int)(low | product);
return mkLong;
}
private short IntLoWord(int word)
{
return (short)(word & short.MaxValue);
}
}
Doing this the NON P/Invoke way...
Override your ListView control and add the following:
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
Message m = Message.Create(this.Handle, 0x127, new IntPtr(0x10001), new IntPtr(0));
this.WndProc(ref m);
}
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
Message m = Message.Create(this.Handle, 0x127, new IntPtr(0x10001), new IntPtr(0));
this.WndProc(ref m);
}
Setting the HotTracking property to true hides the focus rectangle. This repro-ed the Explorer style on my Win7 machine:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class MyListView : ListView {
public MyListView() {
this.HotTracking = true;
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
SetWindowTheme(this.Handle, "explorer", null);
}
[DllImport("uxtheme.dll", CharSet = CharSet.Auto)]
public extern static int SetWindowTheme(IntPtr hWnd, string appname, string subidlist);
}
Beware that getting the items underlined is a side-effect.
Does setting the ListView.ShowFocusCues property to false help?
I know this is rather old, and Windows Forms is antiquated now, but it's still in use and it's still an issue. Worse, none of these solution are elegant, and some don't even work at all.
Here's a very simple solution, when you create your own control that inherits the ListView, then just override the WndProc to never allow focus. It gets rid of all focus-related dotted selection boxes, item selection, subitem selection, etc...
using System.Windows.Forms;
public partial class NoSelectionListView : ListView
{
public NoSelectionListView()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0007) //WM_SETFOCUS
{
return;
}
base.WndProc(ref m);
}
}
It does not seem that there is a particular way to change ListViewItem styles using Windows Forms.
Sometimes there is no way to change some Win32 control behaviors using managed code. The only way is to do some P/Invoke to modify specific behaviors. I find this really tricky but you have no other choice. I often faced this situation when developing Windows Mobile UIs (justly with ListView).
So I have no direct answer to your question but I am pretty sure that if it is not possible using Windows Forms, you can surely do with P/Invoke. The only clues I can give you:
Platform Invoke Tutorial
List View documentation
For me turning focus off didn't work until the control was shown.
I did this:
bool HideFocus { get; set; }
bool _hasEnter;
void OnEnter(object sender, EventArgs e)
{
if (!_hasEnter)
{
_hasEnter = true;
// Selection at startup wont change the actual focus
if (this.SelectedIndices.Count > 0)
this.Items[this.SelectedIndices[0]].Focused = true;
// Hide focus rectangle if requested
if (this.ShowFocusCues && this.HideFocus)
{
var lParam1 = MakeLong(UIS_SET, UISF_HIDEFOCUS);
SendMessage(this.Handle, WM_CHANGEUISTATE, lParam1, 0);
}
}
}
See https://stackoverflow.com/a/15768802/714557 above for the MakeLong call.
Also, any selected items before the control is shown, won't set the selection focus.
I basically used the "Set Focus" event to know that the control was shown and that it was actually getting focus, to make the corretions.

Categories