Я понимаю, что это очень старое сообщение и уже есть отличные ответы, но я хотел сделать простой MCVE, чтобы продемонстрировать один такой подход и позволить новым кодеру быстро увидеть концепцию в действии.
blockquote>В этом примере мы будем использовать 5 файлов:
- Main.java - просто используется для запуска приложения и вызова первого контроллера.
- Controller1.java - контроллер для первого макета FXML.
- Controller2.java - контроллер для второго макета FXML.
- Layout1.fxml - макет FXML для первого сцена.
- Layout2.fxml - макет FXML для второй сцены.
Все файлы перечислены полностью в нижней части этого сообщения.
Цель: показать переданные значения от
Controller1
доController2
и наоборот.Поток программы:
- Первая сцена содержит
TextField
,Button
иLabel
. Когда щелчокButton
щелкнут, второе окно загружается и отображается, включая текст, введенный вTextField
.- Внутри второй сцены есть также
TextField
,Button
, иLabel
.Label
отобразит текст, введенный вTextField
на первой сцене.- При вводе текста во второй сцене
TextField
и нажатииButton
первая сценаLabel
сцены обновлен, чтобы показать введенный текст.Это очень простая демонстрация и, безусловно, может означать некоторое улучшение, но должна сделать концепцию очень понятной.
Сам код также комментируется некоторые детали того, что происходит и как.
КОД
blockquote>Main.java:
import javafx.application.Application; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { // Create the first controller, which loads Layout1.fxml within its own constructor Controller1 controller1 = new Controller1(); // Show the new stage controller1.showStage(); } }
Controller1.java:
import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.stage.Stage; import java.io.IOException; public class Controller1 { // Holds this controller's Stage private final Stage thisStage; // Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller @FXML private TextField txtToSecondController; @FXML private Button btnOpenLayout2; @FXML private Label lblFromController2; public Controller1() { // Create the new stage thisStage = new Stage(); // Load the FXML file try { FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml")); // Set this class as the controller loader.setController(this); // Load the scene thisStage.setScene(new Scene(loader.load())); // Setup the window/stage thisStage.setTitle("Passing Controllers Example - Layout1"); } catch (IOException e) { e.printStackTrace(); } } /** * Show the stage that was loaded in the constructor */ public void showStage() { thisStage.showAndWait(); } /** * The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc. */ @FXML private void initialize() { // Add an action for the "Open Layout2" button btnOpenLayout2.setOnAction(event -> openLayout2()); } /** * Performs the action of loading and showing Layout2 */ private void openLayout2() { // Create the second controller, which loads its own FXML file. We pass a reference to this controller // using the keyword [this]; that allows the second controller to access the methods contained in here. Controller2 controller2 = new Controller2(this); // Show the new stage/window controller2.showStage(); } /** * Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data. */ public String getEnteredText() { return txtToSecondController.getText(); } /** * Allows other controllers to set the text of this layout's Label */ public void setTextFromController2(String text) { lblFromController2.setText(text); } }
Controller2.java:
import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.stage.Stage; import java.io.IOException; public class Controller2 { // Holds this controller's Stage private Stage thisStage; // Will hold a reference to the first controller, allowing us to access the methods found there. private final Controller1 controller1; // Add references to the controls in Layout2.fxml @FXML private Label lblFromController1; @FXML private TextField txtToFirstController; @FXML private Button btnSetLayout1Text; public Controller2(Controller1 controller1) { // We received the first controller, now let's make it usable throughout this controller. this.controller1 = controller1; // Create the new stage thisStage = new Stage(); // Load the FXML file try { FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml")); // Set this class as the controller loader.setController(this); // Load the scene thisStage.setScene(new Scene(loader.load())); // Setup the window/stage thisStage.setTitle("Passing Controllers Example - Layout2"); } catch (IOException e) { e.printStackTrace(); } } /** * Show the stage that was loaded in the constructor */ public void showStage() { thisStage.showAndWait(); } @FXML private void initialize() { // Set the label to whatever the text entered on Layout1 is lblFromController1.setText(controller1.getEnteredText()); // Set the action for the button btnSetLayout1Text.setOnAction(event -> setTextOnLayout1()); } /** * Calls the "setTextFromController2()" method on the first controller to update its Label */ private void setTextOnLayout1() { controller1.setTextFromController2(txtToFirstController.getText()); } }
Layout1.fxml:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> <AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1"> <VBox alignment="CENTER" spacing="10.0"> <padding> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> </padding> <Label style="-fx-font-weight: bold;" text="This is Layout1!"/> <HBox alignment="CENTER_LEFT" spacing="10.0"> <Label text="Enter Text:"/> <TextField fx:id="txtToSecondController"/> <Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/> </HBox> <VBox alignment="CENTER"> <Label text="Text From Controller2:"/> <Label fx:id="lblFromController2" text="Nothing Yet!"/> </VBox> </VBox> </AnchorPane>
Layout2.fxml:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> <AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1"> <VBox alignment="CENTER" spacing="10.0"> <padding> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> </padding> <Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/> <VBox alignment="CENTER"> <Label text="Text From Controller1:"/> <Label fx:id="lblFromController1" text="Nothing Yet!"/> </VBox> <HBox alignment="CENTER_LEFT" spacing="10.0"> <Label text="Enter Text:"/> <TextField fx:id="txtToFirstController"/> <Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/> </HBox> </VBox> </AnchorPane>
У вас есть два основных варианта:
this.Cursor = Cursors.None;
и нарисуйте свой собственный курсор, используя любую технику, которая вам нравится. Затем обновите положение и внешний вид вашего курсора, отвечая на события мыши. Вот два примера: http://www.xamlog.com/2006/07/17/creating-a-custom-cursor/ http://www.hanselman.com/blog /DeveloperDesigner.aspx Если вы решите загрузить из файла, отметьте что вам нужен абсолютный путь к файловой системе для использования конструктора Cursor(string fileName)
. А именно, относительный путь или URI пакета не будут работать. Если вам нужно загрузить курсор из относительного пути или из ресурса, собранного вместе с вашей сборкой, вам нужно будет получить поток из файла и передайте его конструктору Cursor(Stream cursorStream)
.
С другой стороны, указание курсора как относительного пути при загрузке с помощью атрибута XAML делает работу , факт, который вы могли бы использовать для загрузки курсора на скрытый элемент управления, а затем скопируйте ссылку для использования на другом элементе управления. Я не пробовал, но он должен работать.
вы можете сделать это с помощью кода типа
this.Cursor = new Cursor(@"<your address of icon>");
Если вы используете визуальную студию, вы можете
Я знаю, что эта тема уже несколько лет, но вчера я захотел загрузить пользовательский файл курсора из ресурсов проекта и столкнулся с подобными проблемами. Я искал в Интернете решение и не нашел нужное: установить this.Cursor
на пользовательский курсор, который хранится в моей папке ресурсов в моем проекте во время выполнения. Я попробовал решение xaml от Ben, но не нашел его достаточно элегантным. PeterAllen заявил:
А именно, относительный путь или URI пакета не будет работать. Если вам нужно загрузить курсор из относительного пути или из ресурса, собранного вместе с вашей сборкой, вам нужно будет получить поток из файла и передать его в конструктор Cursor (Stream cursorStream). Раздражающий, но факт.
blockquote>Я наткнулся на хороший способ сделать это и разрешил свою проблему:
System.Windows.Resources.StreamResourceInfo info = Application.GetResourceStream(new Uri("/MainApp;component/Resources/HandDown.cur", UriKind.Relative)); this.Cursor = new System.Windows.Input.Cursor(info.Stream);
Как упоминалось выше, Peter, если у вас уже есть .cur-файл, вы можете использовать его как внедренный ресурс, создав фиктивный элемент в разделе ресурсов, а затем ссылаясь на курсор фикчирования, когда вам это нужно.
Например, вы хотите отображать нестандартные курсоры в зависимости от выбранного инструмента.
Добавить в ресурсы:
<Window.Resources>
<ResourceDictionary>
<TextBlock x:Key="CursorGrab" Cursor="Resources/Cursors/grab.cur"/>
<TextBlock x:Key="CursorMagnify" Cursor="Resources/Cursors/magnify.cur"/>
</ResourceDictionary>
</Window.Resources>
Пример встроенного курсора, на который ссылается код:
if (selectedTool == "Hand")
myCanvas.Cursor = ((TextBlock)this.Resources["CursorGrab"]).Cursor;
else if (selectedTool == "Magnify")
myCanvas.Cursor = ((TextBlock)this.Resources["CursorMagnify"]).Cursor;
else
myCanvas.Cursor = Cursor.Arrow;
-Ben
Возможно, он изменился с помощью Visual Studio 2017, но я смог ссылаться на файл .cur как на встроенный ресурс:
<Setter
Property="Cursor"
Value="/assembly-name;component/location-name/curser-name.cur" />
В случае, если кто-то ищет сам UIElement в качестве курсора, я объединил решения Ray и Arcturus :
public Cursor ConvertToCursor(UIElement control, Point hotSpot)
{
// convert FrameworkElement to PNG stream
var pngStream = new MemoryStream();
control.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Rect rect = new Rect(0, 0, control.DesiredSize.Width, control.DesiredSize.Height);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)control.DesiredSize.Width, (int)control.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
control.Arrange(rect);
rtb.Render(control);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
png.Save(pngStream);
// write cursor header info
var cursorStream = new MemoryStream();
cursorStream.Write(new byte[2] { 0x00, 0x00 }, 0, 2); // ICONDIR: Reserved. Must always be 0.
cursorStream.Write(new byte[2] { 0x02, 0x00 }, 0, 2); // ICONDIR: Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid
cursorStream.Write(new byte[2] { 0x01, 0x00 }, 0, 2); // ICONDIR: Specifies number of images in the file.
cursorStream.Write(new byte[1] { (byte)control.DesiredSize.Width }, 0, 1); // ICONDIRENTRY: Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels.
cursorStream.Write(new byte[1] { (byte)control.DesiredSize.Height }, 0, 1); // ICONDIRENTRY: Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels.
cursorStream.Write(new byte[1] { 0x00 }, 0, 1); // ICONDIRENTRY: Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette.
cursorStream.Write(new byte[1] { 0x00 }, 0, 1); // ICONDIRENTRY: Reserved. Should be 0.
cursorStream.Write(new byte[2] { (byte)hotSpot.X, 0x00 }, 0, 2); // ICONDIRENTRY: Specifies the horizontal coordinates of the hotspot in number of pixels from the left.
cursorStream.Write(new byte[2] { (byte)hotSpot.Y, 0x00 }, 0, 2); // ICONDIRENTRY: Specifies the vertical coordinates of the hotspot in number of pixels from the top.
cursorStream.Write(new byte[4] { // ICONDIRENTRY: Specifies the size of the image's data in bytes
(byte)((pngStream.Length & 0x000000FF)),
(byte)((pngStream.Length & 0x0000FF00) >> 0x08),
(byte)((pngStream.Length & 0x00FF0000) >> 0x10),
(byte)((pngStream.Length & 0xFF000000) >> 0x18)
}, 0, 4);
cursorStream.Write(new byte[4] { // ICONDIRENTRY: Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file
(byte)0x16,
(byte)0x00,
(byte)0x00,
(byte)0x00,
}, 0, 4);
// copy PNG stream to cursor stream
pngStream.Seek(0, SeekOrigin.Begin);
pngStream.CopyTo(cursorStream);
// return cursor stream
cursorStream.Seek(0, SeekOrigin.Begin);
return new Cursor(cursorStream);
}
Arrange
в элементе управления приводит к тому, что как ListBoxItems, так и TreeViewItems на мгновение исчезают, только для повторного появления позже, после изменения макетов своих родителей (например, расширения TreeViewItem). Любая идея, почему это так?
– James M
25 April 2016 в 03:27
Вот многофункциональная бесплатная утилита, которая позволяет создавать cur-файл из любого изображения: http://www.rw-designer.com/cursor-maker
Он называется RealWorld Cursor Editor.
И вот ссылка о том, как вставить курсор в проект:
Еще одно решение, похожее на Ray, но вместо медленного и громоздкого копирования пикселей, это использует некоторые внутренние элементы Windows:
private struct IconInfo {
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
[DllImport("user32.dll")]
private static extern IntPtr CreateIconIndirect(ref IconInfo icon);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
public Cursor ConvertToCursor(FrameworkElement cursor, Point HotSpot) {
cursor.Arrange(new Rect(new Size(cursor.Width, cursor.Height)));
var bitmap = new RenderTargetBitmap((int)cursor.Width, (int)cursor.Height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(cursor);
var info = new IconInfo();
GetIconInfo(bitmap.ToBitmap().GetHicon(), ref info);
info.fIcon = false;
info.xHotspot = (byte)(HotSpot.X * cursor.Width);
info.yHotspot = (byte)(HotSpot.Y * cursor.Height);
return CursorInteropHelper.Create(new SafeFileHandle(CreateIconIndirect(ref info), true));
}
В середине есть метод расширения, который я предпочитаю иметь в класс расширения для таких случаев:
using DW = System.Drawing;
public static DW.Bitmap ToBitmap(this BitmapSource bitmapSource) {
var bitmap = new DW.Bitmap(bitmapSource.PixelWidth, bitmapSource.PixelHeight, DW.Imaging.PixelFormat.Format32bppPArgb);
var data = bitmap.LockBits(new DW.Rectangle(DW.Point.Empty, bitmap.Size), DW.Imaging.ImageLockMode.WriteOnly, DW.Imaging.PixelFormat.Format32bppPArgb);
bitmapSource.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);
bitmap.UnlockBits(data);
return bitmap;
}
При этом все довольно просто и просто.
И если вам не нужно указывать свою собственную точку доступа, вы можете даже сократите это сокращение (вам не нужна структура или P / Invokes):
public Cursor ConvertToCursor(FrameworkElement cursor, Point HotSpot) {
cursor.Arrange(new Rect(new Size(cursor.Width, cursor.Height)));
var bitmap = new RenderTargetBitmap((int)cursor.Width, (int)cursor.Height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(cursor);
var icon = System.Drawing.Icon.FromHandle(bitmap.ToBitmap().GetHicon());
return CursorInteropHelper.Create(new SafeFileHandle(icon.Handle, true));
}
info.yHotspot = (byte)(HotSpot.X * cursor.Height);
(должен быть HotSpot.Y, а не HotSpot.X). Этот пример также изменяет диапазон исходного кода точки доступа, масштабируя его по размеру растрового изображения источника, поэтому имейте это в виду при указании вашего смещения.
– Mark Feldman
23 July 2017 в 01:40
Очень простой способ - создать курсор в Visual Studio в качестве .cur-файла, а затем добавить его к ресурсам проектов.
Затем просто добавьте следующий код, если вы хотите назначить курсор :
myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1));
Существует более простой способ, чем управлять отображением курсора самостоятельно или с помощью Visual Studio для создания множества пользовательских курсоров.
Если у вас есть FrameworkElement, вы можете построить из него курсор, используя следующий код:
public Cursor ConvertToCursor(FrameworkElement visual, Point hotSpot)
{
int width = (int)visual.Width;
int height = (int)visual.Height;
// Render to a bitmap
var bitmapSource = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bitmapSource.Render(visual);
// Convert to System.Drawing.Bitmap
var pixels = new int[width*height];
bitmapSource.CopyPixels(pixels, width, 0);
var bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
for(int y=0; y<height; y++)
for(int x=0; x<width; x++)
bitmap.SetPixel(x, y, Color.FromArgb(pixels[y*width+x]));
// Save to .ico format
var stream = new MemoryStream();
System.Drawing.Icon.FromHandle(resultBitmap.GetHicon()).Save(stream);
// Convert saved file into .cur format
stream.Seek(2, SeekOrigin.Begin);
stream.WriteByte(2);
stream.Seek(10, SeekOrigin.Begin);
stream.WriteByte((byte)(int)(hotSpot.X * width));
stream.WriteByte((byte)(int)(hotSpot.Y * height));
stream.Seek(0, SeekOrigin.Begin);
// Construct Cursor
return new Cursor(stream);
}
Обратите внимание, что размер вашего FrameworkElement должен быть стандартным размером курсора (например, 16x16 или 32x32), например:
<Grid x:Name="customCursor" Width="32" Height="32">
...
</Grid>
Он будет использоваться следующим образом:
someControl.Cursor = ConvertToCursor(customCursor, new Point(0.5, 0.5));
Очевидно, что ваш FrameworkElement может быть элементом <Image>
, если у вас есть существующее изображение, или вы можете рисовать все, что вам нравится, используя встроенные инструменты рисования WPF.
Обратите внимание, что Подробные сведения о формате .cur можно найти в ICO (формат файла) .
<Image />
, определенного мной. Отлаживая код, я понял, что var pixels
-array просто содержит 0 для каждого пикселя после запуска CopyPixels()
-метода. Я получил ошибку для stride
-параметра для CopyPixels()
-метода, поэтому я немного изменил код в соответствии с некоторыми другими фрагментами, которые я нашел: int stride = width * ((bitmapSource.Format.BitsPerPixel + 7) / 8);
За исключением того, что код выглядит так же, как и выше. visual
: <Image Height="32" Width="32"/>
– andineupert
30 October 2013 в 21:06
Чтобы использовать пользовательский курсор в XAML, я изменил код Ben McIntosh немного:
<Window.Resources>
<Cursor x:Key="OpenHandCursor">Resources/openhand.cur</Cursor>
</Window.Resources>
Чтобы использовать курсор только ссылку на ресурс:
<StackPanel Cursor="{StaticResource OpenHandCursor}" />
Убедитесь, что любой ресурс GDI (например, bmp.GetHIcon) удаляется. В противном случае вы получите утечку памяти. Следующий код (метод расширения для значка) отлично работает для WPF. Он создает стрелочный указатель с маленьким значком справа внизу.
Примечание. Этот код использует значок для создания курсора. Он не использует текущий контроль пользовательского интерфейса.
Matthias
public static Cursor CreateCursor(this Icon icon, bool includeCrossHair, System.Drawing.Color crossHairColor)
{
if (icon == null)
return Cursors.Arrow;
// create an empty image
int width = icon.Width;
int height = icon.Height;
using (var cursor = new Bitmap(width * 2, height * 2))
{
// create a graphics context, so that we can draw our own cursor
using (var gr = System.Drawing.Graphics.FromImage(cursor))
{
// a cursor is usually 32x32 pixel so we need our icon in the lower right part of it
gr.DrawIcon(icon, new Rectangle(width, height, width, height));
if (includeCrossHair)
{
using (var pen = new System.Drawing.Pen(crossHairColor))
{
// draw the cross-hair
gr.DrawLine(pen, width - 3, height, width + 3, height);
gr.DrawLine(pen, width, height - 3, width, height + 3);
}
}
}
try
{
using (var stream = new MemoryStream())
{
// Save to .ico format
var ptr = cursor.GetHicon();
var tempIcon = Icon.FromHandle(ptr);
tempIcon.Save(stream);
int x = cursor.Width/2;
int y = cursor.Height/2;
#region Convert saved stream into .cur format
// set as .cur file format
stream.Seek(2, SeekOrigin.Begin);
stream.WriteByte(2);
// write the hotspot information
stream.Seek(10, SeekOrigin.Begin);
stream.WriteByte((byte)(width));
stream.Seek(12, SeekOrigin.Begin);
stream.WriteByte((byte)(height));
// reset to initial position
stream.Seek(0, SeekOrigin.Begin);
#endregion
DestroyIcon(tempIcon.Handle); // destroy GDI resource
return new Cursor(stream);
}
}
catch (Exception)
{
return Cursors.Arrow;
}
}
}
/// <summary>
/// Destroys the icon.
/// </summary>
/// <param name="handle">The handle.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public extern static Boolean DestroyIcon(IntPtr handle);
Вы можете попробовать это
<Window Cursor=""C:\WINDOWS\Cursors\dinosaur.ani"" />
Также проверьте BabySmash Скотта Гензельмана (www.codeplex.com/babysmash). Он использовал метод «грубой силы», скрывающий курсор Windows и показывающий его новый курсор на холсте, а затем перемещение курсора в «реальный» курсор был бы
. Подробнее здесь: http://www.hanselman.com/blog/DeveloperDesigner.aspx